solprop.processing.axes#

Rebuild the axes and the layout of a saved run from its grid record.

A saved run carries a solprop.output.schema.GridRecord – scalars only, so that the metadata stays small and its digest cheap – and not the solprop.grid.TimeGrid it was computed on. Everything else in solprop.processing needs the axes back, and solprop.stats needs a grid object, so this module is the one place that rebuilds them.

Two things the rest of the package can then rely on. First, the frequency axis is stored in the layout the kernels use: non-negative and increasing on a field grid, in FFT order about omega0 on an envelope grid (conventions.md section 3). Processing shows an ascending axis, and spectral_order() is the permutation that gets there. Second, the accessors work on the grid’s active band by default, which is what keeps the wavelength axis finite and the selection contiguous.

Module Attributes

SpectralAxis

Which spectral abscissa a density is expressed on.

SPECTRAL_AXES

The spectral abscissae, in SI units: rad/s, Hz and m.

Band

Which part of the stored spectrum an accessor returns.

BANDS

'active' is the propagated band, 'all' every stored bin.

ModeSelection

A mode index, several, the incoherent sum over all of them, or all of them.

Functions

band_mask(grid[, band])

Return the mask of the bins an accessor keeps, on the stored axis.

grid_from_record(record)

Rebuild a grid from the scalars a saved run records.

grid_of(output)

Return the grid a saved run was computed on.

layout(output)

Return the shape of a saved run as (n_z, n_modes, n_pol, n_omega).

spectral_axis(omega, axis)

Return a spectral abscissa and the Jacobian that maps a density onto it.

spectral_order(grid)

Return the permutation that sorts the stored frequency axis ascending.

transverse_units(output)

Return the units a saved run's powers and energies are in.

Classes

TransverseUnits(measure, power, energy[, ...])

The units a run's transverse integrals come out in, for labels.

solprop.processing.axes.BANDS: tuple[Literal['active', 'all'], ...] = ('active', 'all')#

'active' is the propagated band, 'all' every stored bin.

solprop.processing.axes.Band#

Which part of the stored spectrum an accessor returns.

alias of Literal[‘active’, ‘all’]

solprop.processing.axes.ModeSelection = int | collections.abc.Sequence[int] | typing.Literal['sum'] | None#

A mode index, several, the incoherent sum over all of them, or all of them.

solprop.processing.axes.SPECTRAL_AXES: tuple[Literal['omega', 'f', 'wavelength'], ...] = ('omega', 'f', 'wavelength')#

The spectral abscissae, in SI units: rad/s, Hz and m.

solprop.processing.axes.SpectralAxis#

Which spectral abscissa a density is expressed on.

alias of Literal[‘omega’, ‘f’, ‘wavelength’]

class solprop.processing.axes.TransverseUnits(measure, power, energy, intensity='W/m^2', fluence='J/m^2')[source]#

Bases: object

The units a run’s transverse integrals come out in, for labels.

Attributes:
measure{‘area’, ‘per_unit_width’}

What the run’s transverse integrals measure (solprop.modes.protocol.TransverseMeasure).

powerstr

"W", or "W/m" per metre of y.

energystr

"J", or "J/m" per metre of y.

intensitystr

"W/m^2" for both: an intensity is local.

fluencestr

"J/m^2" for both, for the same reason.

Examples

>>> from solprop.processing.axes import TransverseUnits
>>> TransverseUnits.of("per_unit_width").energy
'J/m'
energy: str#
fluence: str = 'J/m^2'#
intensity: str = 'W/m^2'#
measure: Literal['area', 'per_unit_width']#
classmethod of(measure)[source]#

Return the units of a measure.

Parameters:
measure{‘area’, ‘per_unit_width’}

What the transverse integrals measure.

Returns:
TransverseUnits

The labels. The measure is already validated where it is read (solprop.output.schema.RunMetadata.transverse_measure).

power: str#
solprop.processing.axes.band_mask(grid, band='active')[source]#

Return the mask of the bins an accessor keeps, on the stored axis.

'active' is the propagated band omega_min/2 < omega < 1.1 omega_max, which is the only region where the dispersion relation, the noise and the error norm are defined; 'all' keeps every stored bin, including the zero and Nyquist bins of a field grid, where a wavelength is not defined.

Parameters:
gridsolprop.grid.TimeGrid

The grid the stored spectrum lives on.

band{‘active’, ‘all’}

Which part of the axis to keep.

Returns:
numpy.ndarray

Boolean mask, shape (n_omega,), in storage order.

Raises:
solprop.exceptions.ProcessingError

If band is not one of BANDS.

Examples

>>> from solprop.grid import FieldGrid
>>> from solprop.processing.axes import band_mask
>>> grid = FieldGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12)
>>> int(band_mask(grid, "all").sum()) == grid.n_omega
True
solprop.processing.axes.grid_from_record(record)[source]#

Rebuild a grid from the scalars a saved run records.

Every axis and both collars come back exactly: they are functions of the recorded sizes, spacings, band limits and collar edges alone, evaluated by the expressions that built them. The grid exists so that solprop.stats can be called with the definitions it already owns.

Parameters:
recordsolprop.output.schema.GridRecord

The grid record of the run.

Returns:
solprop.grid.TimeGrid

A FieldGrid or EnvelopeGrid, validated.

Raises:
solprop.exceptions.GridError

If the record’s scalars do not satisfy the grid invariants.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.output.schema import GridRecord
>>> from solprop.processing.axes import grid_from_record
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (400e-9, 2e-6), 2e-12)
>>> rebuilt = grid_from_record(GridRecord.from_grid(grid))
>>> rebuilt.kind, bool((rebuilt.t == grid.t).all())
('envelope', True)
solprop.processing.axes.grid_of(output)[source]#

Return the grid a saved run was computed on.

Parameters:
outputsolprop.output.schema.Output

A memory or HDF5 output handler that has been opened.

Returns:
solprop.grid.TimeGrid

The rebuilt grid, as grid_from_record() returns it.

Raises:
solprop.exceptions.OutputError

If the handler carries no metadata, so there is no record to rebuild from.

Examples

>>> from solprop.processing.axes import grid_of
>>> from solprop.processing._examples import example_output
>>> grid_of(example_output()).kind
'envelope'
solprop.processing.axes.layout(output)[source]#

Return the shape of a saved run as (n_z, n_modes, n_pol, n_omega).

The mode and polarisation counts are not in the metadata – a mode-averaged run records no modes at all – so they are read off the stored field, which on an HDF5 handler means reading it.

Parameters:
outputsolprop.output.schema.Output

A memory or HDF5 output handler that has been opened and holds at least one plane.

Returns:
tuple of int

(n_z, n_modes, n_pol, n_omega).

Examples

>>> from solprop.processing.axes import layout
>>> from solprop.processing._examples import example_output
>>> layout(example_output())[1:3]
(1, 1)
solprop.processing.axes.spectral_axis(omega, axis)[source]#

Return a spectral abscissa and the Jacobian that maps a density onto it.

The spectral energy density is per unit of whatever abscissa it is quoted on, so a change of variable carries |d omega / dx|: one for angular frequency, 2 pi for frequency and omega^2 / (2 pi c) = 2 pi c / lambda^2 for wavelength. Units are SI: rad/s, Hz and m, so the densities are J/(rad/s), J/Hz and J/m.

The wavelength abscissa decreases where omega increases; sorting it is the caller’s business, because the same permutation has to be applied to the density.

Parameters:
omeganumpy.ndarray

Angular frequency axis, shape (n_omega,), rad/s.

axis{‘omega’, ‘f’, ‘wavelength’}

The abscissa wanted.

Returns:
xnumpy.ndarray

The abscissa, shape (n_omega,), in rad/s, Hz or m.

jacobiannumpy.ndarray

The factor that multiplies a density per rad/s to give one per unit of x, shape (n_omega,).

Raises:
solprop.exceptions.ProcessingError

If axis is not one of SPECTRAL_AXES, or if a wavelength is asked for on an axis that reaches zero frequency, where it is not defined.

Examples

>>> import numpy as np
>>> from solprop.processing.axes import spectral_axis
>>> x, jacobian = spectral_axis(np.array([2.0e15, 3.0e15]), "f")
>>> float(x[0]) / 1e14, float(jacobian[0])
(3.183098861837907, 6.283185307179586)
solprop.processing.axes.spectral_order(grid)[source]#

Return the permutation that sorts the stored frequency axis ascending.

The identity on a field grid, whose axis is already increasing, and the FFT shift on an envelope grid, whose axis runs from the carrier up and then wraps to the bins below it. Applying it is what conventions.md section 3 means by “processing shifts for display”.

Parameters:
gridsolprop.grid.TimeGrid

The grid the stored spectrum lives on.

Returns:
numpy.ndarray

Indices into the stored axis, shape (n_omega,), such that grid.omega[order] increases.

Examples

>>> import numpy as np
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.processing.axes import spectral_order
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (400e-9, 2e-6), 2e-12)
>>> order = spectral_order(grid)
>>> bool(np.all(np.diff(grid.omega[order]) > 0))
True
solprop.processing.axes.transverse_units(output)[source]#

Return the units a saved run’s powers and energies are in.

A run on modes uniform in y – the hollow planar guide of solprop.modes.slab.SlabMode – records that its transverse integrals are per metre of y (solprop.output.schema.RunMetadata.transverse_measure): its powers, energies and the processing functions that integrate them are then in W/m and J/m, from the same formulas. A handler without metadata is taken to measure an area, as every run did before the key existed.

Parameters:
outputsolprop.output.schema.Output

An opened output handler.

Returns:
TransverseUnits

The unit labels.

Examples

>>> from solprop.processing.axes import transverse_units
>>> from solprop.processing._examples import example_output
>>> transverse_units(example_output()).power
'W'