solprop.stats#
The catalogue of plane statistics, with one definition of each.
Statistics belong to saved planes, not to steps (see docs/dev/decisions.md).
Evaluating every statistic on every accepted step, with a full inverse transform each
time and a root-finding zero-dispersion search among them, would cost about a fifth of a
step and dominate device runs; here they are evaluated where a field is saved, and an
optional periodic subset (StatsSpec.periodic) covers step-level diagnostics.
Every definition here is written once, on internal FFT-convention arrays of shape
(n_modes, n_pol, n_omega), and reduces in a fixed order. There is one
definition per statistic in the run, not a host one and a device one that compute the
same quantity differently, and a backend’s fused pass is validated against it. The
analytic signal comes from solprop.fourier.analytic_signal() alone.
One quantity is measured differently after the run, and deliberately: a run forms its
temporal power as the analytic Poynting flux from the internal amplitude and the
plane’s index, while solprop.processing.time, given a saved plane and no mode,
measures the squared true-power envelope |b(t)|^2. The two differ at second order in
the spread of Re n_eff across the spectrum and have identical time integrals; see
docs/guide/results/processing.md and the docstrings of
solprop.processing.time.peak_power() and
solprop.processing.time.fwhm_t().
Spatial statistics use field units and the geometry’s transverse integration measure.
Their host definitions are in solprop.spatial; periodic backend reductions are
qualified against those definitions. Plasma statistics need the compiled response,
and spatial widths need the geometry declared at assembly.
Undefined is not an error. A mode that carries no light has no spectral centroid,
and a trace that never falls to half its maximum – a continuous wave, an empty mode –
has no full width. Those return nan, which the docstring of each states and the
manual page repeats. A missing dependency, by contrast – a zero-dispersion
wavelength with no mode, a windowed energy with no windows – raises
solprop.exceptions.OutputError.
The manual page is docs/guide/results/statistics.md.
Module Attributes
Statistics that read a |
|
The plasma statistics evaluated on the waveguide's axis (SEP 19, section 5.2). |
|
The field plasma column's run-time integration diagnostics (SEP 18, section 4.5). |
|
Search window of |
|
Every statistic solprop knows about, implemented or reserved, keyed by name. |
|
Every accessor a three-component general modal basis admits, by name. |
|
The general-basis accessors a route withholds when its block couples, by name. |
|
What to use instead of each legacy name on a general modal basis. |
|
Statistics whose value depends on the basis's flux metric, by name. |
|
How an aperture's profile weights the collected spectrum (SEP 23 section 4.5). |
|
The most apertures one |
|
The most rows |
|
Names whose value depends on the modal power weight, so a backend evaluates it. |
Functions
|
Return the time-domain analytic signal of a stored spectrum. |
|
Return the peak of the plane's same-frequency analytic flux, in watts. |
|
Refuse, by name, an aperture profile or weighting that does not exist. |
|
Evaluate every statistic a spec asks for at one saved plane. |
|
Return the statistics a run of the given kind records unless told otherwise. |
|
Return the pulse energy of each mode and polarisation, in joules. |
|
Return the energy inside each spectral window, in joules. |
|
Return the width of the central feature at half maximum, in seconds. |
|
Return the total extent above half maximum, in seconds. |
|
Return the shortest and longest in-band wavelength of a grid, in metres. |
|
Return the centroid of the spectral energy density, per mode and polarisation. |
|
Return the largest instantaneous power each mode reaches, in watts. |
|
Return the largest instantaneous power of the whole field, in watts. |
|
Return the peak power of each mode inside each spectral window, in watts. |
|
Return the physical energy through the plane, in joules. |
|
Return the value of a z-profile at one position. |
|
Reduce species populations at the node of largest total electron density. |
|
Return the physical energy's spectral density, in J/(rad/s). |
|
Return why a general statistic cannot be formed under this weight, or |
|
Return the zero-dispersion wavelength of each mode at one position. |
Classes
|
Circular apertures in a far-field or Fourier plane behind a free-space run. |
|
What one statistic is called, what it returns and what it needs. |
|
Which statistics a run asks for, and the windows and period they need. |
- solprop.stats.ACCESSOR_ADVICE: Mapping[str, str] = {'energy': 'physical_energy, which integrates the same-frequency metric product', 'energy_window': 'physical_energy with an explicit band, or the reconstructed E/H of a saved plane', 'peak_intensity': 'analytic_flux or reconstructed E/H with an explicit detector definition', 'peak_power': 'analytic_flux or reconstructed E/H with an explicit detector definition', 'peak_power_total': 'analytic_flux or reconstructed E/H with an explicit detector definition', 'peak_power_window': 'analytic_flux or reconstructed E/H with an explicit detector definition'}#
What to use instead of each legacy name on a general modal basis.
A name with no entry here gets the generic advice of
solprop.output.capability: a general modal basis hasphysical_energy,analytic_fluxand the reconstruction accessors, and nothing else of that family.
- class solprop.stats.ApertureSet(centres, diameters, distances, edges=None, profile='hard', weighting='amplitude', labels=None)[source]#
Bases:
objectCircular apertures in a far-field or Fourier plane behind a free-space run.
The tables of
aperture_spectrumandaperture_on_axis_spectrum(SEP 23): each aperture collects the light whose paraxial imagex = z c kappa / omegafalls inside it, a chromatic weight on the stored transverse spectrum. Every position is physical: a beam travelling towards positive x lands at positive x. Equality is by identity; the arrays are owned read-only copies.- Attributes:
- centresnumpy.ndarray
Aperture centres
(x, y)in the mask plane, in m, shape(n_apertures, 2).- diametersnumpy.ndarray
Diameters in m, finite and positive, shape
(n_apertures,). On a line the aperture is a slit of this full width along x.- distancesnumpy.ndarray
Distance from the exit face to the mask plane, or a lens’s focal length for a Fourier plane, in m, finite and positive, shape
(n_apertures,).- edgesnumpy.ndarray or None
Edge widths
win m fortanhandrcos, ordersn >= 2forsupergauss, shape(n_apertures,);Noneforhard, and only forhard.- profile{‘hard’, ‘tanh’, ‘rcos’, ‘supergauss’}
The edge profile the set shares (
solprop.spatial.aperture_profile()). The default ishard.- weighting{‘amplitude’, ‘quadrature’}
amplitude(the default) models a mask of amplitude transmissionWand weights the collected spectrum byW**2;quadratureuses a smooth profile once, at the corrected diameter, as a quadrature weight for a hard hole. Quadrature is refused withsupergauss.- labelstuple of str
One unique non-empty label per aperture, recorded with the run’s configuration;
None(the default) labels them"0","1", …
- Raises:
- OutputError
If there are no apertures or more than
MAX_APERTURES; an array is not finite or not of its stated shape; a diameter or distance is not positive; an edge is missing, given forhard, or out of its range (0 < w <= D/4forrcosand fortanhunder amplitude weighting,0 < w <= D/8fortanhunder quadrature weighting,n >= 2forsupergauss); quadrature weighting is asked ofsupergauss; an aperture reaches the paraxial disc’s edge,|X| + D/2 (+ w for rcos) >= z; or the labels are not unique non-empty strings, one per aperture.
- Warns:
- solprop.exceptions.ApertureEdgeWarning
Once per set, for
tanhunder amplitude weighting withr/w < 4(w > D/8): the profile slopes at its centre, so a bright beam there is collected with a lattice error near1e-5. The set is built as asked;rcosis the recommended smooth profile.
Examples
>>> from solprop.stats import ApertureSet >>> holes = ApertureSet( ... centres=[[4e-3, 4e-3], [-4e-3, -4e-3]], ... diameters=[1.5e-3, 1.5e-3], ... distances=[0.25, 0.25], ... edges=[40e-6, 40e-6], ... profile="tanh", ... weighting="quadrature", ... labels=("probe", "signal"), ... ) >>> holes.n_apertures, holes.labels (2, ('probe', 'signal'))
- property n_apertures: int[source]#
Return how many apertures the set holds.
- Returns:
- int
The number of apertures.
- solprop.stats.ApertureWeighting#
How an aperture’s profile weights the collected spectrum (SEP 23 section 4.5).
alias of
Literal[‘amplitude’, ‘quadrature’]
- solprop.stats.CATALOGUE: Mapping[str, StatDefinition] = {'absorbed_power': StatDefinition(name='absorbed_power', summary='Power each absorbing channel of the linear operator removes.', shape='(4,)', units='J/m', requires=('spatial_geometry', 'separable_operator'), implemented=True, backend=True, between_planes=True), 'analytic_flux': StatDefinition(name='analytic_flux', summary='Peak total analytic Poynting flux of a general modal basis.', shape='()', units='W', requires=(), implemented=True, backend=False, between_planes=True), 'aperture_on_axis_spectrum': StatDefinition(name='aperture_on_axis_spectrum', summary='On-axis spectral fluence behind each aperture in a Fourier plane.', shape='(n_apertures, n_omega)', units='J s/m^2', requires=('apertures', 'spatial_geometry'), implemented=True, backend=False, between_planes=False), 'aperture_spectrum': StatDefinition(name='aperture_spectrum', summary='Spectrum collected through each far-field aperture.', shape='(n_apertures, n_omega)', units='J/(rad/s)', requires=('apertures', 'spatial_geometry'), implemented=True, backend=False, between_planes=False), 'beam_radius': StatDefinition(name='beam_radius', summary='Fluence second-moment and 1/e^2 radii; NaN if undefined.', shape='(2,)', units='m', requires=('spatial_geometry',), implemented=True, backend=False, between_planes=False), 'beam_spectrum': StatDefinition(name='beam_spectrum', summary='Whole-beam spectral energy density.', shape='(n_omega,)', units='J/(rad/s)', requires=('spatial_geometry',), implemented=True, backend=False, between_planes=False), 'core_radius': StatDefinition(name='core_radius', summary='Core radius of the waveguide at the plane.', shape='()', units='m', requires=('core_radius',), implemented=True, backend=False, between_planes=True), 'density': StatDefinition(name='density', summary='Number density of the filling gas at the plane.', shape='()', units='1/m^3', requires=('density',), implemented=True, backend=False, between_planes=True), 'electron_density': StatDefinition(name='electron_density', summary='Free-electron density left behind by the pulse.', shape='()', units='1/m^3', requires=(), implemented=True, backend=True, between_planes=True), 'energy': StatDefinition(name='energy', summary='Modal column energies or whole spatial beam energy.', shape='(n_modes, n_pol); () for spatial beams', units='J', requires=(), implemented=True, backend=False, between_planes=True), 'energy_window': StatDefinition(name='energy_window', summary='Pulse energy inside each spectral window.', shape='(n_windows, n_modes, n_pol)', units='J', requires=('windows',), implemented=True, backend=False, between_planes=True), 'fwhm_r': StatDefinition(name='fwhm_r', summary='Central fluence FWHM; NaN if undefined.', shape='()', units='m', requires=('spatial_geometry',), implemented=True, backend=False, between_planes=False), 'fwhm_t': StatDefinition(name='fwhm_t', summary='Central half-maximum width of the total Poynting power.', shape='()', units='s', requires=(), implemented=True, backend=False, between_planes=True), 'fwhm_t_outer': StatDefinition(name='fwhm_t_outer', summary='Total extent of Poynting power above half its maximum.', shape='()', units='s', requires=(), implemented=True, backend=False, between_planes=True), 'omega0': StatDefinition(name='omega0', summary='Centroid of the spectral energy density.', shape='(n_modes, n_pol)', units='rad/s', requires=(), implemented=True, backend=False, between_planes=True), 'on_axis_electron_density': StatDefinition(name='on_axis_electron_density', summary='Free-electron density left behind on the axis.', shape='()', units='1/m^3', requires=(), implemented=True, backend=True, between_planes=False), 'on_axis_intensity': StatDefinition(name='on_axis_intensity', summary='Largest intensity in time on the spatial beam axis.', shape='()', units='W/m^2', requires=('spatial_geometry',), implemented=True, backend=False, between_planes=True), 'on_axis_peak_ionisation_rate': StatDefinition(name='on_axis_peak_ionisation_rate', summary='Peak rate on the axis; cycle-averaged for envelopes.', shape='()', units='1/s', requires=(), implemented=True, backend=True, between_planes=False), 'on_axis_species_electron_density': StatDefinition(name='on_axis_species_electron_density', summary="Each species' electron density left behind on the axis.", shape='(n_species,)', units='1/m^3', requires=(), implemented=True, backend=True, between_planes=False), 'on_axis_species_peak_ionisation_rate': StatDefinition(name='on_axis_species_peak_ionisation_rate', summary='Species peak rate on the axis; cycle-averaged for envelopes.', shape='(n_species,)', units='1/s', requires=(), implemented=True, backend=True, between_planes=False), 'on_axis_spectrum': StatDefinition(name='on_axis_spectrum', summary='On-axis spectral fluence density summed over polarisation.', shape='(n_omega,)', units='J s/m^2', requires=('spatial_geometry',), implemented=True, backend=False, between_planes=False), 'peak_intensity': StatDefinition(name='peak_intensity', summary='Spatial vacuum intensity; modal on-axis analytic Poynting flux.', shape='()', units='W/m^2', requires=(), implemented=True, backend=True, between_planes=True), 'peak_ionisation_rate': StatDefinition(name='peak_ionisation_rate', summary='Peak ionisation rate; cycle-averaged for envelopes.', shape='()', units='1/s', requires=(), implemented=True, backend=True, between_planes=True), 'peak_power': StatDefinition(name='peak_power', summary='Peak analytic Poynting power of each mode during a run.', shape='(n_modes,)', units='W', requires=(), implemented=True, backend=False, between_planes=True), 'peak_power_total': StatDefinition(name='peak_power_total', summary='Peak total analytic Poynting power during a run.', shape='()', units='W', requires=(), implemented=True, backend=False, between_planes=True), 'peak_power_window': StatDefinition(name='peak_power_window', summary='Peak analytic Poynting power per mode and spectral window.', shape='(n_windows, n_modes)', units='W', requires=('windows',), implemented=True, backend=False, between_planes=True), 'physical_energy': StatDefinition(name='physical_energy', summary='Physical energy of a general modal basis, metric-weighted.', shape='()', units='J', requires=(), implemented=True, backend=False, between_planes=True), 'plasma_boundary_level': StatDefinition(name='plasma_boundary_level', summary='Free-current integrand at the window start, relative to its peak.', shape='()', units='1', requires=(), implemented=True, backend=True, between_planes=False), 'plasma_undershoot': StatDefinition(name='plasma_undershoot', summary="Largest exposure undershoot of the plasma column's band integral.", shape='()', units='1', requires=(), implemented=True, backend=True, between_planes=False), 'plasma_work_defect': StatDefinition(name='plasma_work_defect', summary='Loss-current work against the ionisation it paid, relative.', shape='()', units='1', requires=(), implemented=True, backend=True, between_planes=False), 'pressure': StatDefinition(name='pressure', summary='Pressure of the filling gas at the plane.', shape='()', units='bar', requires=('pressure',), implemented=True, backend=False, between_planes=True), 'quadrature_error': StatDefinition(name='quadrature_error', summary='Relative error of the transverse quadrature on the polarisation.', shape='()', units='dimensionless', requires=(), implemented=True, backend=True, between_planes=True), 'region_spectrum': StatDefinition(name='region_spectrum', summary='Spectrum in each box of transverse wavevector.', shape='(n_regions, n_omega)', units='J/(rad/s)', requires=('regions', 'spatial_geometry'), implemented=True, backend=False, between_planes=False), 'species_density': StatDefinition(name='species_density', summary='Component number densities at this plane.', shape='(n_species,)', units='1/m^3', requires=(), implemented=True, backend=True, between_planes=False), 'species_electron_density': StatDefinition(name='species_electron_density', summary='Species electron densities at the densest total-electron node.', shape='(n_species,)', units='1/m^3', requires=(), implemented=True, backend=True, between_planes=True), 'species_ionisation': StatDefinition(name='species_ionisation', summary='Final ionised fraction of each species at every node.', shape='(n_species, n_nodes)', units='1', requires=(), implemented=True, backend=True, between_planes=True), 'species_ionisation_energy': StatDefinition(name='species_ionisation_energy', summary='Final ionised inventory energy per propagation length.', shape='(n_species,)', units='J/m', requires=(), implemented=True, backend=True, between_planes=False), 'species_peak_ionisation_rate': StatDefinition(name='species_peak_ionisation_rate', summary='Species peak rate over all nodes; cycle-averaged for envelopes.', shape='(n_species,)', units='1/s', requires=(), implemented=True, backend=True, between_planes=False), 'spectral_flux': StatDefinition(name='spectral_flux', summary="Spectral density of a general modal basis's physical energy.", shape='(n_omega,)', units='J/(rad/s)', requires=(), implemented=True, backend=False, between_planes=False), 'zdw': StatDefinition(name='zdw', summary='Zero-dispersion wavelength of each mode at the plane.', shape='(n_modes,)', units='m', requires=('modes',), implemented=True, backend=False, between_planes=False)}#
Every statistic solprop knows about, implemented or reserved, keyed by name.
- solprop.stats.DEFAULT_ZDW_RANGE = (1e-07, 3e-06)#
Search window of
zdw()when no narrower one is given, m.
- solprop.stats.GENERAL_MODAL_ACCESSORS: frozenset[str] = frozenset({'analytic_flux', 'channel_labels', 'coordinate_intensity', 'electric_field', 'fwhm_t', 'fwhm_t_outer', 'instantaneous_flux', 'magnetic_field', 'omega0', 'physical_energy', 'plane_nodes', 'reconstructed_flux', 'spectral_flux', 'zdw'})#
Every accessor a three-component general modal basis admits, by name.
The sets live here, beside the catalogue whose names they select from, and
solprop.output.capabilityimports them to build the gate: they are a statement about statistics, and keeping them here is what letssolprop.translate.generalrecord the list a file publishes without a translation module importing an output one.omega0,fwhm_tandfwhm_t_outerare functionals of the same-frequency fluxa.H W aand take the basis metric (solprop.stats.METRIC_WEIGHTED_STATISTICS), so they are exact on a declared basis as well as on the identity one;zdwis a property of the channels’ propagation constants and of no basis at all – until a declared basis couples channels whose propagation constants differ, when the block executes through a coupled flow and each channel’s constant is the block’s mean; the route then withholds it (WITHHELD_WITH_A_FLOW).spectral_fluxis the one array of the family – the same flux as a density in frequency rather than integrated. It is admitted but not a default of the route: it isn_omegawide per plane where the other five are scalars, so a run asks for it by name.Two names of
solprop.processing.modalare deliberately absent.quadrature_erroris refused separately, by the driver, because what disqualifies it is the absent embedded coarse rule and not the basis – a general guide’s node rule has no embedded pair to difference, so such a run has no transverse-resolution alarm, which is a known limitation – and it sits inside the schema-7 resume key, so publishing it here would promise a statistic the same record refuses.channel_amplitudestakes two arrays and a table rather than an opened handler, so no door ever asks the gate about it; it is insolprop.output.capability._GENERAL_ONLYall the same, so that the one direction that could be reached – an ordinary run asked for it – would be refused for the right reason.
- solprop.stats.MAX_APERTURES = 16#
The most apertures one
ApertureSetcarries.
- solprop.stats.MAX_REGIONS = 16#
The most rows
StatsSpec.spectral_regionscarries.
- solprop.stats.METRIC_WEIGHTED_STATISTICS: frozenset[str] = frozenset({'analytic_flux', 'fwhm_t', 'fwhm_t_outer', 'omega0', 'physical_energy', 'spectral_flux'})#
Statistics whose value depends on the basis’s flux metric, by name.
These are the functionals of the physical same-frequency flux
sum_mn conj(a_m) W_mn a_n– its integral (physical_energy), its density in frequency (spectral_flux), its peak (analytic_flux), its two widths (fwhm_t,fwhm_t_outer) and the spectral centroid of its per-channel share (omega0). On an orthonormal basisWis the identity and they are the unweighted quantities they have always been, bit for bit; on a general modal basis whose Gram is not the identity they are wrong without it, so a caller that has a metric must hand it over (collect()) and one that cannot must refuse rather than answer (solprop.processing.modal).zdwis deliberately absent: it is a property of the channels’ propagation constants and of no basis at all, and a unitary change of basis of an exactly degenerate block leaves every one of them unchanged.
- solprop.stats.ON_AXIS_PLASMA_STATISTICS = ('on_axis_electron_density', 'on_axis_peak_ionisation_rate', 'on_axis_species_electron_density', 'on_axis_species_peak_ionisation_rate')#
The plasma statistics evaluated on the waveguide’s axis (SEP 19, section 5.2).
Opt-in and saved-plane only. The axis field is synthesised from the mode profiles, as for
peak_intensity, and its populations are scanned with exactly the rate tables, preionisation, densities and running-integral rule of the node scan, once per observation; a rule with a node on the axis supplies that row instead. They sit beside the unchanged node reductions –electron_densityis the densest node’s – because a few-node field-scale rule may have no node near the axis, and a multimode field’s largest density need not lie on it. Defined on resolved modal routes; the spatial routes, general modal guides, the mode-averaged route and TDSE refuse them by name.
- solprop.stats.PLASMA_INTEGRATION_STATISTICS = ('plasma_undershoot', 'plasma_work_defect', 'plasma_boundary_level')#
The field plasma column’s run-time integration diagnostics (SEP 18, section 4.5).
Evaluated with the other plasma statistics at each save plane. Each is a ratio of two maxima over every sample, node and population, so that a node the field barely ionises cannot set it: the undershoot
max(-dS) / max(dS)of the exposure increments; the work defectmax |paid - freed| / max freed, the loss current’s work per ionisation potential against the fraction it freed; and the boundary level, the free-current integrand’s largest magnitude in the2Ksamples the filter reads past the window’s start against its largest anywhere.
- solprop.stats.PROFILE_STATISTICS = ('density', 'pressure', 'core_radius')#
Statistics that read a
solprop.profiles.ZProfileat the plane’s position.
- class solprop.stats.StatDefinition(name, summary, shape, units, requires=(), implemented=True, backend=False, between_planes=False)[source]#
Bases:
objectWhat one statistic is called, what it returns and what it needs.
- Attributes:
- namestr
Key the statistic is stored under.
- summarystr
One line saying what the number is, used by the manual page.
- shapestr
Shape of the value in the standard axis names,
'()'for a scalar.- unitsstr
SI units of the value.
- requirestuple
What
collect()must be given for this statistic:'windows','modes', the name of a z-profile,'spatial_geometry', the far-field tables'apertures'and'regions'of the spec, or'separable_operator', the run’s linear operator, which only a backend holds.- implementedbool
Whether the statistic exists yet. A reserved name is in the catalogue so that a spec asking for it is refused now instead of silently returning nothing.
- backendbool
Whether only a backend can compute it. These are the statistics of quantities the saved field does not carry – the electron density, the ionisation rate, the on-axis intensity, the quadrature error – which a backend forms from its own buffers in one pass.
collect()refuses them by name rather than pulling the state back across the seam to reconstruct them.- between_planesbool
Whether a backend can also evaluate it between save planes, from what an accepted step already leaves behind, for the periodic channel of
StatsSpec.periodic. What disqualifies a statistic is cost per step, not difficulty:zdwneeds a root search per mode, and paying that on every step would cost a large share of the step;peak_intensityis in the subset because its on-axis matrix is built once and rescaled rather than rebuilt.StatsSpecrefuses a periodic name without it, by name.
Examples
>>> from solprop.stats import CATALOGUE >>> CATALOGUE["energy"].units, CATALOGUE["energy"].shape ('J', '(n_modes, n_pol); () for spatial beams') >>> CATALOGUE["electron_density"].backend True >>> CATALOGUE["energy"].between_planes, CATALOGUE["zdw"].between_planes (True, False)
- class solprop.stats.StatsSpec(names=(), energy_windows=<factory>, periodic=(), period=1, apertures=None, spectral_regions=<factory>)[source]#
Bases:
objectWhich statistics a run asks for, and the windows and period they need.
This is the object of the kernel contract §8: the translation layer carries it into a backend, the driver hands it to
collect(), and both read the same names. Equality is by identity (eq=False) becauseenergy_windowsis an array.- Attributes:
- namestuple
Statistics to evaluate at every saved plane, in the order they are evaluated. Every name must be in
CATALOGUEand must be implemented.- energy_windowsnumpy.ndarray
Spectral windows, shape
(n_windows, 2), rad/s, each row(lo, hi)withlo < hi. Infinite edges are allowed, so that a pair of windows can tile the whole axis. Required byenergy_windowandpeak_power_window.- periodictuple
The subset of
namesa backend also evaluates between planes, for step-level diagnostics. Must be a subset ofnames, and every entry must carryStatDefinition.between_planes: a statistic whose cost is only bearable once per plane is refused here rather than made the run’s per-step cost.- periodint
How many accepted steps apart the periodic subset is evaluated, at least 1.
- aperturesApertureSet or None
Far-field apertures, required by
aperture_spectrumandaperture_on_axis_spectrum(SEP 23).Noneby default, and then left out of the run’s configuration record, so every earlier record keeps its hash.- spectral_regionsnumpy.ndarray
Boxes of physical transverse wavevector in rad/m, shape
(n_regions, 4), rows(kx_lo, kx_hi, ky_lo, ky_hi), each a half-open box[lo, hi)on both axes, at mostMAX_REGIONSrows, infinite edges allowed. Required byregion_spectrum. Empty by default, and then left out of the configuration record.
- Raises:
- OutputError
If a name is unknown, reserved or repeated; if a window or a region is malformed; if
periodicis not a subset ofnamesor names a statistic no backend evaluates between planes; ifperiodis below 1; or if a name needs a far-field table the spec does not carry.
Examples
>>> import numpy as np >>> from solprop.stats import StatsSpec >>> spec = StatsSpec( ... names=("energy", "energy_window"), ... energy_windows=np.array([[2.0e15, 3.0e15]]), ... ) >>> spec.n_windows 1
- apertures: ApertureSet | None = None#
- energy_windows: Float[ndarray, 'n_windows 2']#
- property n_apertures: int[source]#
Return how many far-field apertures the spec carries.
- Returns:
- int
The apertures of
apertures, or zero without them.
- property n_regions: int[source]#
Return how many regions of transverse wavevector the spec carries.
- Returns:
- int
The rows of
spectral_regions.
- property n_windows: int[source]#
Return how many spectral windows the spec carries.
- Returns:
- int
The number of windows.
- same_tables(other)[source]#
Return whether another spec carries the same far-field tables.
- Parameters:
- otherStatsSpec
The spec to compare, for example a lockstep batch member’s.
- Returns:
- bool
True when both carry no apertures or the same apertures entry for entry, and identical regions: a structural comparison.
- spectral_regions: Float[ndarray, 'n_regions 4']#
- solprop.stats.WEIGHT_DEPENDENT_STATISTICS = frozenset({'analytic_flux', 'energy', 'energy_window', 'fwhm_t', 'fwhm_t_outer', 'omega0', 'peak_intensity', 'peak_power', 'peak_power_total', 'peak_power_window', 'physical_energy', 'spectral_flux'})#
Names whose value depends on the modal power weight, so a backend evaluates it.
- solprop.stats.WITHHELD_WITH_A_FLOW: frozenset[str] = frozenset({'zdw'})#
The general-basis accessors a route withholds when its block couples, by name.
A declared change of basis over channels whose propagation constants differ executes through
linear.coupled_flow, and the per-channel propagation constant the statistics would read is then the block’s mean:zdwfrom it is a number that propagates nothing. The route leaves these names out of the record’s published list and every accessor door refuses them withsolprop.output.capability.refusal_for()’swithheld.
- solprop.stats.analytic_field(grid, field)[source]#
Return the time-domain analytic signal of a stored spectrum.
The one construction of the analytic signal in the package, so that the statistics, the backends’ on-axis intensity and anything else that needs a power agree by construction. It serves both grids: an envelope grid’s state already is the analytic signal with the carrier removed, so it only has to be transformed back; a field grid’s is the analytic signal of the real field.
solprop.fourier.analytic_signal()neither doubles the zero-frequency bin nor discards the Nyquist bin, because only that convention makes the real part an identity and the two energy functionals an exact Parseval pair.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(..., n_omega), internal convention.
- Returns:
- numpy.ndarray
Complex analytic signal, shape
(..., n_t), whose modulus squared is the instantaneous power in watts.
- solprop.stats.analytic_flux(grid, field, *, metric=None)[source]#
Return the peak of the plane’s same-frequency analytic flux, in watts.
The general modal basis’s name for a peak power:
max_t sum_m |a_m(t)|^2, the same-frequency diagonal of the Poynting flux, which is all a modal state can say about a power on its own.It is not the cross-section flux, and the difference is physics. The physical
int Re(E_a x conj(H_a))/2 . z dAof the reconstructed fields carries the guide’s cross-frequency flux kernelK_ij = sum_q w_q [e_x(w_i) conj(h_y(w_j)) - e_y(w_i) conj(h_x(w_j))]/2, whose diagonal a provider normalises to one and whose off-diagonal it says nothing about. The two peaks therefore differ, bounded term by term bysum_ij |B_i||B_j||K_ij - 1|and measured at 1.3e-5 of the peak on the general-basis test fixture, inside a bound of 3.4e-5: even a positive spectral metric does not make an instantaneous cross-frequency flux a sum of coordinate powers. The time integrals do agree, to 5.0e-7 there, because the cross-frequency terms are an integer number of cycles in the window. For the flux itself, reconstructed from the guide’s stored reference, usesolprop.processing.modal.reconstructed_flux(), a trace in time whose peak is this statistic plus the cross-frequency terms; its real-field partner with the sum-frequency term issolprop.processing.modal.instantaneous_flux().The arithmetic is exactly ``peak_power_total``’s on the identity metric, which is what
metric=Noneevaluates, bit for bit. With a metric it is the same peak taken on the whitened amplitudesc = W^(1/2) aof_whitened, so that the quantity peaked is the physical same-frequency fluxsum_mn conj(a_m) W_mn a_nand not a sum of coordinate powers. The old name is refused on this basis rather than redefined:peak_power_totalpromises that sum of coordinate powers, and on a basis that is not orthonormal there is no such sum to promise.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention, in the square root of watts.- metricnumpy.ndarray or None, optional
The basis flux metric
W(omega), shape(n_modes, n_modes, n_omega), dimensionless;None, the default, is the identity.
- Returns:
- numpy.ndarray
Peak same-frequency analytic flux, a zero-dimensional array, W.
- Raises:
- solprop.exceptions.OutputError
If metric disagrees in shape with field, or is not positive definite at some frequency, so that no orthonormal basis exists to measure in.
Examples
>>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import analytic_flux, peak_power_total >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> pulse = GaussianPulse(wavelength=800e-9, fwhm=30e-15, peak_power=1e9) >>> built = pulse.build(grid) >>> bool(analytic_flux(grid, built) == peak_power_total(grid, built)) True
- solprop.stats.check_aperture_choices(profile, weighting)[source]#
Refuse, by name, an aperture profile or weighting that does not exist.
The check
ApertureSetmakes of its two choices. It takes any object, because a caller outside the test session’s runtime type checker can pass anything.- Parameters:
- profileobject
The edge profile asked for.
- weightingobject
The weighting asked for.
- Raises:
- OutputError
If either is not one of its choices.
Examples
>>> from solprop.stats import check_aperture_choices >>> check_aperture_choices("tanh", "quadrature") is None True
- solprop.stats.collect(spec, grid, plane, *, modes=None, profiles=None, flux=None, metric=None)[source]#
Evaluate every statistic a spec asks for at one saved plane.
Each statistic uses its named public array function and deterministic reductions. For saved true-power modal amplitudes, temporal array functions use
|b(t)|². Backend run statistics use analytic Poynting flux: their time integrals agree, but broadband peaks and widths can differ (docs/dev/conventions.mdsection 4).- Parameters:
- specStatsSpec
Which statistics to evaluate, and the windows they need.
- gridsolprop.grid.TimeGrid
The grid the plane’s field lives on.
- planesolprop.output.schema.SavedPlane
The plane, whose field is in the internal FFT convention.
- modescollections.abc.Sequence, optional
The propagated modes, in state order. Needed by
zdw.- profilescollections.abc.Mapping, optional
The z-profiles, keyed by statistic name. Needed by
density,pressureandcore_radius.- fluxsolprop.spatial.SpatialFlux or None, optional
The plane’s measure for a radial or Cartesian spectrum, resolved at the plane’s own position (and jump side) in its medium: every spatial statistic is that medium’s axial Poynting flux (SEP 34), and widths use the signed time-integrated fluence. The far-field statistics
aperture_spectrum,aperture_on_axis_spectrum,region_spectrumandbeam_spectrumneed a Cartesian grid or a line (SEP 23), and are formed in one pass bysolprop.spatial.aperture_statistics(), the function a run uses.- metricnumpy.ndarray or None, optional
The basis flux metric
W(omega)at this plane, shape(n_modes, n_modes, n_omega), for the names inMETRIC_WEIGHTED_STATISTICS;None, the default, is the identity and every statistic is then bitwise what it was. A caller that holds a general modal problem gets it fromsolprop.translate.coupled.metric_at().
- Returns:
- dict
The value of each requested statistic, keyed by name, always as a NumPy array and zero-dimensional for a scalar. The shape of each is the
shapefield of itsStatDefinition.
- Raises:
- OutputError
If a statistic’s dependency is missing – no windows for a windowed statistic, no modes for
zdw, or no profile for a profile statistic – or if it is one a backend owns, which this function cannot form from a saved plane (absorbed_poweramong them, refused by name because a plane does not carry the linear operator’s rates); if a far-field statistic is asked of a radial grid, or of a line with an aperture offy = 0or a region excludingkappa_y = 0; or if the plane kept no field (SEP 22).
Examples
>>> import numpy as np >>> from solprop.grid import EnvelopeGrid >>> from solprop.output.schema import SavedPlane >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import collect, default_spec >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> built = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6).build(grid) >>> plane = SavedPlane(z=0.0, field=built) >>> values = collect(default_spec("mode_averaged"), grid, plane) >>> sorted(values) ['energy', 'fwhm_t', 'fwhm_t_outer', 'omega0', 'peak_power'] >>> values["energy"].shape, values["fwhm_t"].shape ((1, 1), ())
- solprop.stats.default_spec(kind, *, with_profiles=False, with_zdw=False)[source]#
Return the statistics a run of the given kind records unless told otherwise.
The field statistics every run wants, plus – for a multimode run, where the modes peak at different positions in time – the peak of the total power. The z-profile statistics and the zero-dispersion wavelength are opt-in: the first needs profiles the driver may not have, and the second is a root search per mode per plane.
- Parameters:
- kind{‘mode_averaged’, ‘multimode’, ‘radial’, ‘free’}
Which transform the run uses.
- with_profilesbool, optional
Whether to record the gas density, the pressure and the core radius for modal runs, or only the gas density for spatial runs.
- with_zdwbool, optional
Whether to record the zero-dispersion wavelength of each mode. Spatial runs reject this option because they carry no modal dispersion.
- Returns:
- StatsSpec
The spec, with no energy windows; add those for the run’s bands of interest.
Examples
>>> from solprop.stats import default_spec >>> default_spec("mode_averaged").names ('omega0', 'energy', 'peak_power', 'fwhm_t', 'fwhm_t_outer') >>> "peak_power_total" in default_spec("multimode").names True
- solprop.stats.energy(grid, field)[source]#
Return the pulse energy of each mode and polarisation, in joules.
This is
solprop.fourier.energy_omega(), the exact discrete form ofdocs/dev/conventions.md§4, which agrees with the time-domain functional to round-off. No quadrature weights (such as an extended Simpson rule on a field grid) are derived: those would differ from this by their fourth-order quadrature error.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention, in the square root of watts.
- Returns:
- numpy.ndarray
Energy, shape
(n_modes, n_pol), J.
Examples
>>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import energy >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> pulse = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6) >>> f"{float(energy(grid, pulse.build(grid))[0, 0]):.6e}" '1.000000e-06'
- solprop.stats.energy_window(grid, field, windows)[source]#
Return the energy inside each spectral window, in joules.
The gate is hard and half-open,
lo <= omega < hi, so windows that tile the band partition the bins and their energies sum to the total exactly.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention.- windowsnumpy.ndarray
Window edges, shape
(n_windows, 2), rad/s, each row(lo, hi). Convert a wavelength pair withomega = 2 pi c / wavelength, remembering that it reverses the order.
- Returns:
- numpy.ndarray
Energy per window, shape
(n_windows, n_modes, n_pol), J.
Examples
>>> import numpy as np >>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import energy, energy_window >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> built = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6).build(grid) >>> tiling = np.array([[-np.inf, 2.4e15], [2.4e15, np.inf]]) >>> total = energy_window(grid, built, tiling).sum() >>> bool(np.isclose(total, energy(grid, built).sum(), rtol=1e-15)) True
- solprop.stats.fwhm_t(grid, field, *, metric=None)[source]#
Return the width of the central feature at half maximum, in seconds.
The total power
sum_modes sum_pol |E_a(t)|^2is formed, the half-maximum level taken from its global peak with no baseline subtraction, and the crossings found by walking outwards from that peak to the first sample below the level on either side and interpolating linearly. Structure further out – a dispersive wave, a soliton that has walked off, a noise pedestal – does not widen the answer, which is why this is the number to read as a pulse duration. Usefwhm_t_outer()for the total extent above half maximum.On a basis whose flux Gram is not the identity this takes the metric: the trace whose width is measured is then the physical same-frequency flux
analytic_flux()peaks, evaluated on the whitened amplitudes of_whitened, so that a run does not report the width of a quantity whose peak it does not report.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention.- metricnumpy.ndarray or None, optional
The basis flux metric
W(omega), shape(n_modes, n_modes, n_omega), dimensionless;None, the default, is the identity.
- Returns:
- numpy.ndarray
The width as a zero-dimensional array, s. A trace that never falls to half its maximum – a continuous wave, an empty plane – has no width and gives
nan.
- Raises:
- solprop.exceptions.OutputError
If metric disagrees in shape with field, or is not positive definite at some frequency, so that no orthonormal basis exists to measure in.
Examples
>>> import numpy as np >>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import fwhm_t >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> built = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6).build(grid) >>> bool(np.isclose(fwhm_t(grid, built), 30e-15, rtol=1e-4)) True
- solprop.stats.fwhm_t_outer(grid, field, *, metric=None)[source]#
Return the total extent above half maximum, in seconds.
The first and the last sample anywhere in the window that exceed half the peak power, interpolated outwards. On a clean single pulse this equals
fwhm_t(); on a structured trace it spans every satellite, so the pair together says how much of the energy sits outside the central feature.It takes the metric for the reason
fwhm_t()does.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention.- metricnumpy.ndarray or None, optional
The basis flux metric
W(omega), shape(n_modes, n_modes, n_omega), dimensionless;None, the default, is the identity.
- Returns:
- numpy.ndarray
The extent as a zero-dimensional array, s, or
nanwhen the trace reaches the edge of the window still above half its maximum, or carries no power.
- Raises:
- solprop.exceptions.OutputError
If metric disagrees in shape with field, or is not positive definite at some frequency, so that no orthonormal basis exists to measure in.
Examples
>>> import numpy as np >>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import fwhm_t, fwhm_t_outer >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> built = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6).build(grid) >>> bool(np.isclose(fwhm_t_outer(grid, built), fwhm_t(grid, built), rtol=1e-12)) True
- solprop.stats.inband_wavelengths(grid)[source]#
Return the shortest and longest in-band wavelength of a grid, in metres.
The search window
zdw()uses by default whencollect()calls it: a zero-dispersion point outside the simulated band says nothing about the run, and hunting for one there drives every material model into extrapolation.- Parameters:
- gridsolprop.grid.TimeGrid
The grid.
- Returns:
- tuple of float
(shortest, longest)in m, from the grid’s in-band frequency limits.
Examples
>>> from solprop.grid import EnvelopeGrid >>> from solprop.stats import inband_wavelengths >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> [f"{value * 1e9:.1f}" for value in inband_wavelengths(grid)] ['400.0', '2000.0']
- solprop.stats.omega0(grid, field, *, metric=None)[source]#
Return the centroid of the spectral energy density, per mode and polarisation.
\[\omega_0 = \frac{\sum_k \omega_k\,S(\omega_k)}{\sum_k S(\omega_k)}\]with \(S\) from
solprop.fourier.spectral_density(). The prefactor of the density cancels in the ratio, so this is the first moment of \(|\tilde E|^2\) over the frequency axis. The sum runs over every bin, including the zero-frequency one: outside the active band a propagated state is identically zero, so the bins there contribute nothing.Because the centroid is an energy-weighted mean, the whole field’s centroid is the energy-weighted mean of these, which is why the statistic is per column rather than summed.
On a basis whose flux Gram is not the identity this takes the metric, and the columns are then the whitened ones of
_whitened: a coordinate column of a nonorthogonal basis carries no separable spectral energy at all – the columns interfere – so a centroid of|a_m(omega)|^2would be a promise the basis cannot keep.W^(1/2)is the identity plus half the basis’s Gram departure, so on a unitary change of basis of a certified orthonormal reference, which is the only kind this package executes, the whitened column is the declared channel to within that departure and the centroid moves by no more.An empty column is empty relative to the plane. A column whose density sums to at most one machine epsilon of the largest column’s has no centroid and is
nan. That floor is relative rather than exactly zero because whitening mixes: the unlaunched partner of a launched circular channel carriesW^(1/2)times the other’s amplitude – 1e-32 of it on this route’s own fixture – and an exact-zero test would report a finite centroid of the whitening’s rounding noise for a channel nothing was launched into.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention.- metricnumpy.ndarray or None, optional
The basis flux metric
W(omega), shape(n_modes, n_modes, n_omega), dimensionless;None, the default, is the identity and is the bitwise unchanged path.
- Returns:
- numpy.ndarray
Centroid frequency, shape
(n_modes, n_pol), rad/s. A column carrying no energy has no centroid and isnan.
- Raises:
- solprop.exceptions.OutputError
If metric disagrees in shape with field, or is not positive definite at some frequency, so that no orthonormal basis exists to measure in.
Examples
>>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import omega0 >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> pulse = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6) >>> f"{float(omega0(grid, pulse.build(grid))[0, 0]):.6e}" '2.354564e+15'
- solprop.stats.peak_power(grid, field)[source]#
Return the largest instantaneous power each mode reaches, in watts.
max_t sum_pol |E_a(t)|^2: the polarisation components of one mode are summed before the maximum is taken, because they are two components of one field and their powers add at each instant. The analytic signal is formed once for the whole call. This array function measures squared true-power amplitude; run statistics use analytic Poynting flux, with the arithmetic-mean frequency kernel instead.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention.
- Returns:
- numpy.ndarray
Peak power, shape
(n_modes,), W.
Examples
>>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import peak_power >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> pulse = GaussianPulse(wavelength=800e-9, fwhm=30e-15, peak_power=1e9) >>> f"{float(peak_power(grid, pulse.build(grid))[0]):.6e}" '1.000000e+09'
- solprop.stats.peak_power_total(grid, field)[source]#
Return the largest instantaneous power of the whole field, in watts.
max_t sum_modes sum_pol |E_a(t)|^2: the modes are summed before the maximum, so this is the peak of the total power and not the largest of the per-mode peaks, which arrive at different times.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention.
- Returns:
- numpy.ndarray
Peak total power, a zero-dimensional array, W.
Examples
>>> import numpy as np >>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import peak_power, peak_power_total >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> pulse = GaussianPulse(wavelength=800e-9, fwhm=30e-15, peak_power=1e9) >>> built = pulse.build(grid) >>> f"{float(peak_power_total(grid, built)):.6e}" '1.000000e+09'
- solprop.stats.peak_power_window(grid, field, windows)[source]#
Return the peak power of each mode inside each spectral window, in watts.
The spectrum is gated with the same hard half-open window as
energy_window()and transformed back, so this is the peak power of the light in that band. One scalar per mode per window.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention.- windowsnumpy.ndarray
Window edges, shape
(n_windows, 2), rad/s, each row(lo, hi).
- Returns:
- numpy.ndarray
Peak power, shape
(n_windows, n_modes), W.
Examples
>>> import numpy as np >>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import peak_power_window >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> pulse = GaussianPulse(wavelength=800e-9, fwhm=30e-15, peak_power=1e9) >>> built = pulse.build(grid) >>> whole = np.array([[-np.inf, np.inf]]) >>> f"{float(peak_power_window(grid, built, whole)[0, 0]):.6e}" '1.000000e+09'
- solprop.stats.physical_energy(grid, field, *, metric=None)[source]#
Return the physical energy through the plane, in joules.
The general modal basis’s name for an energy, and the one number of that family that survives a change of coordinates: the same-frequency product of the state with the basis’s flux metric,
sum_k sum_mn conj(a_m) W_mn a_n, integrated over frequency. A coordinate energy is not additive whenWis not the identity, and this total is, which is why it is a scalar andenergyis an array.On the identity metric this is exactly ``sum(energy(grid, field))``, which is what
metric=Noneevaluates, bit for bit. With a metric it is the same functional of the whitened amplitudesc = W^(1/2) aof_whitened, which is the definition above and not a second one:sum_m |c_m|^2 = sum_mn conj(a_m) W_mn a_nat every frequency, and the frequency integral is the Parseval weight ofdocs/dev/conventions.mdsection 4 either way.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention, in the square root of watts.- metricnumpy.ndarray or None, optional
The basis flux metric
W(omega), shape(n_modes, n_modes, n_omega), dimensionless;None, the default, is the identity.
- Returns:
- numpy.ndarray
Physical energy, a zero-dimensional array, J.
- Raises:
- solprop.exceptions.OutputError
If metric disagrees in shape with field, or is not positive definite at some frequency, so that no orthonormal basis exists to measure in.
Examples
>>> import numpy as np >>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import energy, physical_energy >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> built = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6).build(grid) >>> bool(physical_energy(grid, built) == energy(grid, built).sum()) True
- solprop.stats.profile_value(profile, z)[source]#
Return the value of a z-profile at one position.
The one implementation behind the
density,pressureandcore_radiusstatistics, which differ only in which profile they read; the catalogue gives each its own units.- Parameters:
- profilesolprop.profiles.ZProfile
The path along the waveguide.
- zfloat
Position, m.
- Returns:
- numpy.ndarray
The value as a zero-dimensional array, in the profile’s own units. Those are SI except for a pressure path, which is in bar: pressures are the one documented exception at the user interface (
docs/dev/conventions.md§1) and asolprop.profiles.PressureGradientis a user-interface object.
Examples
>>> from solprop.profiles import PressureGradient >>> from solprop.stats import profile_value >>> gradient = PressureGradient.two_point(1.0, 2.0, 0.0) >>> f"{float(profile_value(gradient, 0.75)):.6f}" '1.000000'
- solprop.stats.species_statistics(densities, fractions, peak_rates, potentials, weights)[source]#
Reduce species populations at the node of largest total electron density.
- Parameters:
- densitiesnumpy.ndarray
Component number densities in m^-3, in mixture order.
- fractionsnumpy.ndarray
Final ionised fractions at each node, shape (n_species, n_nodes). Components without an ionisation channel have zero entries.
- peak_ratesnumpy.ndarray
Largest rate over all nodes per species in s^-1; cycle-averaged on envelope grids.
- potentialsnumpy.ndarray
Ionisation potentials in J, zero for components without channels.
- weightsnumpy.ndarray
Physical transverse quadrature weights in m². The mode-averaged case uses its effective area as its single weight.
- Returns:
- dict
The five species statistics plus total electron density and peak rate.
species_ionisation_energyis final ionised inventory in J/m, including preionisation. Pulse-deposited ionisation work counts only newly freed electrons and must subtract the initial fractions before this reduction.
- Raises:
- OutputError
If a population, rate, potential or quadrature weight is outside its domain.
- solprop.stats.spectral_flux(grid, field, *, metric=None)[source]#
Return the physical energy’s spectral density, in J/(rad/s).
The spectrum analogue of
physical_energy(), and the one array of the general modal family: the same same-frequency product of the state with the basis’s flux metric,sum_mn conj(a_m) W_mn a_n, published per bin instead of integrated over frequency. It is a density in the sense ofdocs/dev/conventions.mdsection 4 –dU/domega, the prefactor that pairs with the shot-noise amplitude so that one photon in a bin givesS(omega_k) domega = hbar omega_k– and it issolprop.fourier.spectral_density()summed over the channel axis, which is what makes it additive where a coordinate spectrum is not.It is indexed by frequency and by nothing else. A per-channel spectral energy is no more additive than a per-channel energy on a basis whose Gram is not the identity, so the channel axis is summed after the whitening and not published.
The frequency sum is ``physical_energy``, exactly on every interior bin:
domegatimes this density is the4 dt / n_tweight ofsolprop.fourier.energy_omega()there. The zero-frequency and Nyquist bins are the documented exception – the analytic signal does not double them, so summing the density over every bin overcounts those two – and both are identically zero on any gridsolprop.gridbuilds, whose spectral collar apodises them away. Askphysical_energy()when an exact energy is wanted.- Parameters:
- gridsolprop.grid.TimeGrid
The grid the spectrum lives on.
- fieldnumpy.ndarray
Stored spectrum, shape
(n_modes, n_pol, n_omega), internal convention, in the square root of watts.- metricnumpy.ndarray or None, optional
The basis flux metric
W(omega), shape(n_modes, n_modes, n_omega), dimensionless;None, the default, is the identity.
- Returns:
- numpy.ndarray
Spectral energy density, shape
(n_omega,), J/(rad/s).
- Raises:
- solprop.exceptions.OutputError
If metric disagrees in shape with field, or is not positive definite at some frequency, so that no orthonormal basis exists to measure in.
Examples
>>> import numpy as np >>> from solprop.grid import EnvelopeGrid >>> from solprop.pulses import GaussianPulse >>> from solprop.stats import physical_energy, spectral_flux >>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 1e-12) >>> built = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6).build(grid) >>> density = spectral_flux(grid, built) >>> density.shape == (grid.n_omega,) True >>> total = float(np.sum(density) * grid.d_omega) >>> bool(np.isclose(total, float(physical_energy(grid, built)), rtol=1e-14)) True
- solprop.stats.weight_refusal_for(names, weight)[source]#
Return why a general statistic cannot be formed under this weight, or
None.physical_energy(),spectral_flux()andanalytic_flux()evaluate the identity-metric, unit-weight arithmetic, which is what the general modal route certifies before it will run (linear.power_weightis the constant one there). This is unreachable today: no route carries both a modal table and a non-constant weight, and the translation layer refuses to build one. It is written because the failure would be silent rather than loud if a later change loosened either side – the three names are outside the set a backend routes through the weighted Poynting pass, so they would be answered from the unweighted field.- Parameters:
- namescollections.abc.Sequence
The statistic names asked for.
- weightnumpy.ndarray
The modal power weight at this plane, shape
(n_modes, n_pol, n_omega).
- Returns:
- str or None
The refusal, or
Nonewhen no general statistic is asked for or the weight is the constant one.
Examples
>>> import numpy as np >>> from solprop.stats import weight_refusal_for >>> weight_refusal_for(["physical_energy"], np.ones((1, 1, 4))) is None True >>> weight_refusal_for(["energy"], np.full((1, 1, 4), 2.0)) is None True
- solprop.stats.zdw(modes, z, *, wavelength_range=(1e-07, 3e-06))[source]#
Return the zero-dispersion wavelength of each mode at one position.
Off by default: it is a root search on
beta_2per mode, which is slow and does not belong on every accepted step. Each call stands alone: no previous answer is carried over as the next search’s starting guess, so one failure cannot poison every later plane withnan.- Parameters:
- modescollections.abc.Sequence
The propagated modes, in state order.
- zfloat
Position along the waveguide, m.
- wavelength_rangetuple of float, optional
Shortest and longest wavelength in m to bracket the root in. Defaults to a 100 nm to 3 um window;
collect()narrows it to the grid’s own band withinband_wavelengths(), which is where the answer means anything and where the material models are not extrapolating.
- Returns:
- numpy.ndarray
Zero-dispersion wavelength, shape
(n_modes,), m.
- Raises:
- solprop.exceptions.ModeError
If a mode has no zero-dispersion point in the search window, rather than a missing value that would propagate silently.
Examples
>>> from solprop.modes import MarcatiliMode >>> from solprop.stats import zdw >>> mode = MarcatiliMode(125e-6, "He", 0.4, model="reduced", cladding=1.45) >>> f"{zdw([mode], 0.0, wavelength_range=(150e-9, 900e-9))[0] * 1e9:.1f}" '376.7'