solprop.translate.spatial#

Freeze spatial geometry and separable coefficients for the kernel contract.

These records contain arrays and scalars only. Geometry is independent of the temporal grid. A separable operator keeps frequency-only material coefficients and one transverse-frequency square per column, avoiding a full coefficient volume. Kernel schema 15 adds the hybrid planar kind "slab" (slab modes in x, an FFT in y) and the one per-channel table it needs, kappa_sq.

Functions

aperture_tables(spec, grids, geometry)

Form the far-field statistics' tables once, for every backend (SEP 23 §6).

axial_admittance(coefficients, omega, columns)

Return the dimensionless forward admittance c k_z / omega of a chunk.

axial_factors(coefficients, omega, columns)

Return a chunk's admittance and absorption rates for the far-field statistics.

longitudinal_root(coefficients[, columns])

Return the longitudinal wave number, its propagating test and its field rate.

rate_bounds(coefficients)

Bound the real part of a separable operator without forming it.

separable_values(coefficients[, columns])

Evaluate a separable operator and prefactor for the host reference.

Classes

FluxPlan(base[, material, admission])

The plane-measure coefficients of a spatial run at any position (SEP 34).

RateBounds(loss, gain, propagating, ...)

Bounds on a separable operator's real part, for the exponent budget.

SeparableOperator(k_squared, kperp_sq, ...)

Store a constant spatial operator and prefactor without coefficient volumes.

SpatialGeometry(kind, shape, axes, ...)

Carry spatial axes, quadrature, reconstruction and absorption across the seam.

class solprop.translate.spatial.FluxPlan(base, material=None, admission=None)[source]#

Bases: object

The plane-measure coefficients of a spatial run at any position (SEP 34).

Kernel schema 35. The run’s statistics read the axial admittance of the medium at the plane they measure through at(): a uniform medium’s is the run’s own separable coefficients, and a radial gas path’s replaces the material square by its z table, so the root is longitudinal_root()’s at that position.

Attributes:
baseSeparableOperator

Transverse squares, wall terms and active band, with the material square at the entrance.

materialsolprop.translate.chebyshev.PiecewiseChebyshev or None

The complex material square (omega n(omega, z) / c)**2 in m^-2 along z, value shape (n_omega,), for a radial medium that varies; None for a uniform medium.

admissionsolprop.adiabatic.AdiabaticAdmission or None

The varying path’s adiabatic certificate (SEP 34 section 4.5); None for a uniform medium, which needs none.

admission: AdiabaticAdmission | None = None#
at(z)[source]#

Return the coefficients of the plane at z.

Parameters:
zfloat

Position in m inside the translated span.

Returns:
SeparableOperator

The coefficients whose root is the plane’s k_z.

base: SeparableOperator#
material: PiecewiseChebyshev | None = None#
class solprop.translate.spatial.RateBounds(loss, gain, propagating, evanescent, spectral, transverse)[source]#

Bases: object

Bounds on a separable operator’s real part, for the exponent budget.

SEP 22 section 4.5: the decaying factor exp(lambda_minus h) of the inverse propagator and the growing one exp(lambda_plus h) of a gain medium overflow once their exponent passes log(DBL_MAX). Each bound is the largest over the frequencies of a sufficient per-frequency bound, and the terms are those of the frequency where the loss bound is largest.

Attributes:
lossfloat

lambda_minus, a bound on max(-Re D) in m^-1, nonnegative.

gainfloat

lambda_plus, a bound on max(Re D) in m^-1, nonnegative.

propagatingfloat

The largest propagating attenuation at the binding frequency, m^-1.

evanescentfloat

The capped evanescent attenuation there, min(|Im k_z|, evanescent_rate).

spectralfloat

Half the spectral rate there.

transversefloat

Half the transverse collar’s largest rate, applied on active frequencies.

evanescent: float#
gain: float#
loss: float#
propagating: float#
spectral: float#
transverse: float#
class solprop.translate.spatial.SeparableOperator(k_squared, kperp_sq, k_squared_low, kperp_sq_low, frame, spectral_rate, prefactor_numerator, prefactor_clamped, active, evanescent_rate, n_pol, z_span, kappa_sq=None, kperp_rate=None)[source]#

Bases: object

Store a constant spatial operator and prefactor without coefficient volumes.

Attributes:
k_squarednumpy.ndarray

Shared complex material wave-number square in m^-2, shape (n_omega,).

kperp_sqnumpy.ndarray

Transverse wave-number square in m^-2, shape (n_modes,), nonnegative.

k_squared_lownumpy.ndarray

Complex residual of the material square in m^-2, shape (n_omega,).

kperp_sq_lownumpy.ndarray

Real residual of the transverse square in m^-2, shape (n_modes,).

framenumpy.ndarray

Shared real frame subtraction in m^-1, shape (n_omega,).

spectral_ratenumpy.ndarray

Shared spectral power absorption in m^-1, shape (n_omega,), nonnegative.

prefactor_numeratornumpy.ndarray

Shared purely imaginary numerator of the nonlinear prefactor, with the temporal crop scale included, shape (n_omega,).

prefactor_clampednumpy.ndarray

Shared prefactor when the real longitudinal square is nonpositive, with the temporal crop scale included, shape (n_omega,).

activenumpy.ndarray

Physical frequency mask, bool, shape (n_omega,).

evanescent_ratefloat

Largest evanescent field attenuation rate in m^-1, positive and finite.

n_polint

State polarisation components, 1 or 2; every coefficient is shared by them.

z_spantuple of float

Valid propagation interval in m, finite and increasing.

kappa_sqnumpy.ndarray or None

Kernel schema 15, the hybrid planar kind only: each slab channel’s complex transverse wave-number square kappa_p(omega)**2 = k_squared - beta_p(omega)**2 in m^-2, shape (n_channels, n_omega), where beta_p = omega n_eff,p / c is the mode’s own complex propagation constant (the wall term under the mode’s loss policy). Column c belongs to channel c % n_channels (the channel index fastest). Zero at inactive frequencies. None (the default) for every other kind, which leaves the operator exactly as before.

kperp_ratenumpy.ndarray or None

Kernel schema 31: the transverse-wavevector power absorption rate alpha_perp of a solprop.boundaries.TransverseCollar, in m^-1, one per column, shape (n_modes,), finite and nonnegative. It enters the operator on the active band only, beside the spectral rate, D -= alpha_perp / 2 (SEP 22 section 5.3), and never the unitary phase. None (the default) is stored as zeros, whose operator is today’s to the last bit. Refused nonzero on the hybrid planar kind.

Raises:
TranslationError

If arrays, scales, frequency counts or the valid interval disagree, or the transverse rate is negative, misshaped or set on the hybrid planar kind.

active: Bool[ndarray, 'n_active']#
evanescent_rate: float#
frame: Float[ndarray, 'n_frame']#
k_squared: Complex[ndarray, 'n_k']#
k_squared_low: Complex[ndarray, 'n_k_low']#
kappa_sq: Complex[ndarray, 'n_channels n_kappa'] | None = None#
kperp_rate: Float[ndarray, 'n_kperp_rate'] | None = None#
kperp_sq: Float[ndarray, 'n_transverse']#
kperp_sq_low: Float[ndarray, 'n_transverse_low']#
property n_channels: int[source]#

Return the number of slab channels the columns cycle through.

Returns:
int

The rows of kappa_sq, or 1 when it is absent.

n_pol: int#
prefactor_clamped: Complex[ndarray, 'n_clamped']#
prefactor_numerator: Complex[ndarray, 'n_numerator']#
spectral_rate: Float[ndarray, 'n_spectral_rate']#
property value_shape: tuple[int, int, int, int][source]#

Return the full state shape represented by these shared coefficients.

Returns:
tuple of int

(1, n_modes, n_pol, n_omega) without allocating that volume.

z_span: tuple[float, float]#
class solprop.translate.spatial.SpatialGeometry(kind, shape, axes, spectral_axes, spatial_weights, spectral_weights, coordinates, window, absorber_rate, on_axis, forward_scale, inverse_scale)[source]#

Bases: object

Carry spatial axes, quadrature, reconstruction and absorption across the seam.

Attributes:
kind{‘radial’, ‘free’, ‘slab’}

Radial QDHT, Cartesian FFT, or the hybrid planar transform: slab modes in x (the transform’s own synthesis and projection on the slab rule) times an FFT in y. Modal transforms carry no such record.

shapetuple of int

(n_r,) or (n_x, n_y): the physical node grid. Cartesian column ix + n_x*iy has x fastest. Every entry is at least 2, except n_y = 1 for a line uniform in y, whose one y sample is y = k_y = 0 with unit measure (per metre of y). For "slab", n_x counts the slab rule’s x nodes and the state’s columns are (p, k_y), p + n_p*iy with the mode index fastest, so n_modes = n_p*n_y while n_nodes = n_x*n_y.

axestuple of numpy.ndarray

(r,) or (x, y), float64 in m, strictly increasing. A slab’s x axis is its quadrature nodes inside the gap, not uniformly spaced.

spectral_axestuple of numpy.ndarray

(k_r,) or (k_x, k_y), in rad/m; for "slab" (k_y,) alone, the x direction’s spectrum being the mode index. Cartesian axes are in FFT order.

spatial_weightsnumpy.ndarray

Physical area weights in m^2, shape (n_nodes,) in node order.

spectral_weightsnumpy.ndarray

Parseval weights for the stored transverse spectrum, shape (n_modes,). Units are m^-2 for radial Hankel spectra and m^2 for Cartesian raw DFTs. A slab column is a modal amplitude in sqrt(W/m) and its weight 2 dy / (eps0 c n_y) is the raw DFT’s dy/n_y divided by eps0 c / 2, in m ohm, so that the common eps0 c / 2 energy measure gives joules on every kind.

coordinatesnumpy.ndarray

(r, 0) or (x, y) per physical node, in m, shape (n_nodes, 2).

windownumpy.ndarray

Spatial collar window in [0, 1], shape (n_nodes,). For "slab" it is the y collar alone, the same at every x node of a row.

absorber_ratenumpy.ndarray

In-stage spatial power absorption rate, in 1/m, shape (n_nodes,); for "slab" a function of y alone, like window.

on_axisnumpy.ndarray

Linear reconstruction weights from transverse spectral columns to the axis, complex128, shape (n_modes,). Temporal oversampling is not folded in.

forward_scalefloat

Common transverse forward scaling; m^2 radially, dimensionless for the FFT.

inverse_scalefloat

Common transverse inverse scaling; m^-2 radially, dimensionless for the FFT (1/n_y for the slab’s y transform).

Raises:
TranslationError

If dimensions, axes, measures, collar values or scalings disagree.

absorber_rate: Float[ndarray, 'n_rate']#
axes: tuple[Float[ndarray, '...'], ...]#
coordinates: Float[ndarray, 'n_points 2']#
forward_scale: float#
inverse_scale: float#
kind: Literal['radial', 'free', 'slab']#
property n_columns: int[source]#

Return the number of state columns the geometry describes.

Returns:
int

n_r or n_x*n_y for the radial and Cartesian kinds, where columns and nodes coincide, and n_p*n_y for a slab, where they do not.

on_axis: Complex[ndarray, 'n_axis_columns']#
shape: tuple[int, ...]#
spatial_weights: Float[ndarray, 'n_area']#
spectral_axes: tuple[Float[ndarray, '...'], ...]#
spectral_weights: Float[ndarray, 'n_spectral_area']#
window: Float[ndarray, 'n_window']#
solprop.translate.spatial.aperture_tables(spec, grids, geometry)[source]#

Form the far-field statistics’ tables once, for every backend (SEP 23 §6).

The per-aperture constants – the profile’s half-width, D/2 or, under quadrature weighting, the corrected D_q/2 of solprop.spatial.quadrature_half_width() (unchanged on a line, whose edges are straight) – and the scale table of solprop.spatial.aperture_scale(), with the geometry’s column axes, weights and on-axis vector and the spec’s regions. Every backend calls this at construction and solprop.stats.collect() calls it for a saved plane, so all of them use the same scale to the bit.

Parameters:
specsolprop.stats.StatsSpec

The request and its far-field tables.

gridssolprop.translate.masks.Grids or solprop.grid.TimeGrid

Anything carrying the physical frequency axis omega in rad/s.

geometrySpatialGeometry, solprop.grid.FreeGrid, solprop.grid.RadialGrid or None

The run’s geometry record or a saved plane’s grid.

Returns:
solprop.spatial.ApertureTables or None

The tables, or None when the spec names no far-field statistic.

Raises:
OutputError

For a radial or planar-hybrid geometry or none at all; and on a line, for an aperture whose centre is off y = 0 or a region whose kappa_y interval excludes zero, which would select nothing.

solprop.translate.spatial.axial_admittance(coefficients, omega, columns)[source]#

Return the dimensionless forward admittance c k_z / omega of a chunk.

The physical axial admittance is eps0 c times this value (SEP 34). It is the root of longitudinal_root() on active propagating bins at positive frequency, in the optics convention (positive imaginary part for loss), and zero elsewhere: a nonpropagating or inactive column is not a forward power port.

Parameters:
coefficientsSeparableOperator

The plane’s or the run’s constant coefficients.

omeganumpy.ndarray

Physical angular frequencies in rad/s, shape (n_omega,).

columnsslice

The transverse columns of the chunk.

Returns:
numpy.ndarray

Complex admittance, shape (n_chunk, n_omega).

Examples

>>> import numpy as np
>>> from solprop.constants import C
>>> from solprop.translate.spatial import SeparableOperator, axial_admittance
>>> omega = np.array([0.0, C, 2 * C])
>>> vacuum = SeparableOperator(
...     k_squared=(omega / C) ** 2 + 0j, kperp_sq=np.array([0.0, 2.25]),
...     k_squared_low=np.zeros(3, complex), kperp_sq_low=np.zeros(2),
...     frame=np.zeros(3), spectral_rate=np.zeros(3),
...     prefactor_numerator=np.zeros(3, complex),
...     prefactor_clamped=np.zeros(3, complex),
...     active=np.array([False, True, True]), evanescent_rate=200.0, n_pol=1,
...     z_span=(0.0, 1.0),
... )
>>> np.round(axial_admittance(vacuum, omega, slice(None)).real, 12).tolist()
[[0.0, 1.0, 1.0], [0.0, 0.0, 0.661437827766]]
solprop.translate.spatial.axial_factors(coefficients, omega, columns)[source]#

Return a chunk’s admittance and absorption rates for the far-field statistics.

The weights of the far-field statistics (SEP 23 sections 4.8 and 4.9, measured by SEP 34’s axial flux), from longitudinal_root(). The admittance is axial_admittance(). The four power rates, in the order of solprop.spatial.ABSORBED_CHANNELS, are the operator’s spectral rate on every bin, as the stages apply it; the transverse collar’s rate on active bins (zero without one); 2 min(|Im k_z|, cap) on active evanescent bins; and 2 Im k_z on active propagating bins, the material loss (negative for gain). Together they are twice the negated real part of the operator; weighted by the real admittance, their power is the physical flux the linear flow removes. The evanescent channel, whose admittance is zero, removes no physical flux.

Parameters:
coefficientsSeparableOperator

The run’s constant coefficients.

omeganumpy.ndarray

Physical angular frequencies in rad/s, shape (n_omega,).

columnsslice

The transverse columns of the chunk.

Returns:
tuple of numpy.ndarray

The dimensionless complex admittance, shape (n_chunk, n_omega), and the rates in m^-1, shape (4, n_chunk, n_omega).

solprop.translate.spatial.longitudinal_root(coefficients, columns=None)[source]#

Return the longitudinal wave number, its propagating test and its field rate.

The one evaluation of the root the stages use: the principal square root k_z of the compensated square q = (k^2 - k_perp^2) + (k_low^2 - k_perp,low^2) (with a slab channel’s wall term subtracted last), the test Re q > 0 that separates propagating from evanescent columns, and the field attenuation, Im k_z where propagating and min(|Im k_z|, evanescent_rate) where not. separable_values() builds the operator from it, and the far-field statistics read it (axial_factors()), so both see the same root (SEP 23 section 6).

Parameters:
coefficientsSeparableOperator

Constant coefficients in the internal convention.

columnsslice or None, optional

Transverse columns to evaluate; None selects all columns.

Returns:
tuple of numpy.ndarray

k_z in m^-1 (complex), the propagating mask (bool) and the field attenuation in m^-1, each of shape (n_columns, n_omega). Inactive bins are not masked here: every consumer applies coefficients.active.

solprop.translate.spatial.rate_bounds(coefficients)[source]#

Bound the real part of a separable operator without forming it.

The search of solprop.spatial.attenuation_extremes() per slab channel (one on every kind but the hybrid planar), so the cost is O(n_omega log n_modes). On an active frequency the loss bound is the larger of the propagating attenuation’s positive part and the capped evanescent one, plus half the largest transverse rate, plus half the spectral rate; on an inactive one it is half the spectral rate alone (SEP 22, Q6). The gain bound is the negated smallest propagating attenuation where it is negative.

Parameters:
coefficientsSeparableOperator

The operator.

Returns:
RateBounds

The bounds and the binding frequency’s terms.

Examples

>>> import numpy as np
>>> from solprop.translate.spatial import SeparableOperator, rate_bounds
>>> operator = SeparableOperator(
...     k_squared=np.array([0j, 4 + 0j]), kperp_sq=np.array([0.0, 9.0]),
...     k_squared_low=np.zeros(2, complex), kperp_sq_low=np.zeros(2),
...     frame=np.zeros(2), spectral_rate=np.array([6.0, 0.0]),
...     prefactor_numerator=np.zeros(2, complex),
...     prefactor_clamped=np.zeros(2, complex),
...     active=np.array([False, True]), evanescent_rate=2.0, n_pol=1,
...     z_span=(0.0, 1.0), kperp_rate=np.array([0.0, 4.0]),
... )
>>> bounds = rate_bounds(operator)
>>> bounds.loss, bounds.evanescent, bounds.transverse
(4.0, 2.0, 2.0)
solprop.translate.spatial.separable_values(coefficients, columns=None)[source]#

Evaluate a separable operator and prefactor for the host reference.

The reference may select bounded transverse columns. Compiled backends evaluate the same formula per element and retain only shared vectors.

Parameters:
coefficientsSeparableOperator

Constant coefficients, already validated and in the internal convention.

columnsslice or None, optional

Transverse columns to evaluate; None selects all columns.

Returns:
tuple of numpy.ndarray

Linear rate in m^-1, shape (1, n_modes, n_pol, n_omega), and nonlinear prefactor, shape (n_modes, n_pol, n_omega).