solprop.processing.modal#

Physical E and H on a saved plane of a general modal run, and what they measure.

A general modal guide propagates channel amplitudes, and its file stores those: the mode axis is a coordinate axis and not a physical field. This module is the way back. Given the run’s own reference tables – the ones solprop.translate.general.general_modal_route() built, rebuilt from the guide the file names – it reconstructs E(x, y, t) and H(x, y, t) in V/m and A/m at the guide’s quadrature nodes, and forms the quantities a detector would see: the cycle-averaged reconstructed_flux(), the real instantaneous_flux() with its sum-frequency term, and the per-coordinate coordinate_intensity(), which is not additive.

What a file carries and what it does not. Output schema 7 records the guide’s identity – the channel labels, the provider, the sampling, the table and metric identities – and not the reference arrays themselves, which are tens of megabytes per run and are rebuilt from the guide in tens of milliseconds (28 ms on the slice’s one-channel 288-node fixture; about 0.71 s if the guide has still to be solved, which is where that cost is). Physical profile storage for output is separate from the execution tables and may be a hash-identified sidecar. Every function here therefore takes the tables beside the handler and checks them against the stored identity, path by path, refusing the first entry that disagrees, so a reconstruction is never made from the wrong guide. guide= is required beside them, and its sampling and its regions are checked too, which the tables’ identity cannot see: a guide re-sampled at other interior knots, or declaring another chi3, builds tables whose identity agrees entry for entry. Every caller has the guide, because that is what the tables were rebuilt from, so there is no reading of this module in which the narrower check is the one wanted; the keyword has no default and no alias.

A guide whose geometry moves along z is reconstructed at each plane’s own position: the guide is solved again there on the rule that has moved with its interfaces, so the fields come back on that position’s nodes and the flux is weighted by that position’s areas. channel_amplitudes() recovers amplitudes on the entrance reference only.

The accessor whitelist. These names are admitted on a general modal basis and refused on every other, because an orthogonal modal run and a spatial run carry neither the certified metric nor the E/H reference they promise (solprop.output.capability). The refusal is the same text the run loop uses.

The manual page is docs/guide/results/output.md.

Functions

channel_amplitudes(electric, magnetic, tables)

Recover channel amplitudes from a nodal E/H pair, in the square root of watts.

channel_labels(output)

Return the ordered channel labels a file records for its mode axis.

coordinate_intensity(output, tables, *[, ...])

Return the flux density each channel would carry alone, in W/m^2.

electric_field(output, tables, *[, plane, ...])

Return the electric field at the guide's nodes on one saved plane, in V/m.

instantaneous_flux(output, tables, *[, ...])

Return the instantaneous Poynting flux of one saved plane, in watts.

magnetic_field(output, tables, *[, plane, ...])

Return the magnetic field at the guide's nodes on one saved plane, in A/m.

physical_energy(output, *[, plane, z])

Return the physical energy through a plane, or through every plane, in joules.

plane_nodes(output, tables, *[, plane, z])

Return where one saved plane is reconstructed: its nodes and area weights.

reconstructed_flux(output, tables, *[, ...])

Return the cycle-averaged Poynting flux of one saved plane, in watts.

solprop.processing.modal.channel_amplitudes(electric, magnetic, tables)[source]#

Recover channel amplitudes from a nodal E/H pair, in the square root of watts.

The inverse of the synthesis electric_field() and magnetic_field() perform, through the coefficient dual of ModalTransformTables: a_m = sum_q w_q [W^-1]_mn (E x conj(h_n) + conj(e_n) x H)_z / 4, the same Hermitian pairing the flux metric is built from. It is amplitude recovery and not source projection, which carries the separate -i omega factor.

Reprojecting a physical field that was not synthesised from this basis creates a new initial-value problem; it is not a resume path.

Parameters:
electricnumpy.ndarray

Nodal electric spectrum, shape (n_nodes, 3, n_omega), V/m, internal convention.

magneticnumpy.ndarray

Nodal magnetic spectrum, same shape, A/m.

tablessolprop.translate.modal_tables.ModalTransformTables

The reference the fields were synthesised from, carrying its coordinate_dual.

Returns:
numpy.ndarray

The amplitudes, shape (n_modes, n_omega), square root of watts.

Raises:
solprop.exceptions.OutputError

If the record carries no coefficient dual, or the fields do not match its node and frequency extents.

solprop.processing.modal.channel_labels(output)[source]#

Return the ordered channel labels a file records for its mode axis.

Output schema 7 persists one solprop.modes.protocol.ChannelLabel per row of the mode axis, so a plot legend, a selection by name and a resume check all read the same identity. A file written before schema 7 named its channels by class and position and carries none.

Parameters:
outputsolprop.output.schema.Output

An opened output handler.

Returns:
tuple

One label per mode-axis row, in state order.

Raises:
solprop.exceptions.OutputError

If the handler holds no record, or the record carries no labels.

Examples

>>> from solprop.processing._examples import example_general_output
>>> from solprop.processing.modal import channel_labels
>>> [label.display_label for label in channel_labels(example_general_output())]
['HE1,1:even']
solprop.processing.modal.coordinate_intensity(output, tables, *, plane=None, z=None, guide)[source]#

Return the flux density each channel would carry alone, in W/m^2.

The coordinate decomposition of the intensity: channel m’s own analytic flux density Re(E^m x conj(H^m))/2 . z, with E^m synthesised from that channel alone. It is what a per-mode intensity means on a modal basis.

It is not additive. The sum over channels of these densities is the physical flux density only where the channels do not interfere; in general the difference is the cross term, which is exactly what reconstructed_flux() with density carries and this does not. Even a positive spectral metric does not make an instantaneous cross-frequency flux a sum of coordinate powers. Nothing is clipped, here or there.

Parameters:
outputsolprop.output.schema.Output

An opened output handler holding a general modal run.

tablessolprop.translate.modal_tables.ModalTransformTables

The run’s own reference; its identity is checked against the file’s.

planeint or None, optional

Index of the plane wanted, negative from the end.

zfloat or None, optional

Position of the plane wanted, m. Exactly one of plane and z is given.

guidesolprop.modes.guide.ModalGuide

The guide the reference was rebuilt from, checked as in electric_field(). Required.

Returns:
numpy.ndarray

The per-channel flux density, shape (n_modes, n_nodes, n_t), W/m^2.

Raises:
solprop.exceptions.OutputError

If the run is not a general modal one, the tables or the guide are not the ones that produced the file, or no single plane was selected.

solprop.processing.modal.electric_field(output, tables, *, plane=None, z=None, analytic=False, convention='optics', guide)[source]#

Return the electric field at the guide’s nodes on one saved plane, in V/m.

E_i(x_q, t) = sum_m e_i^m(x_q, omega) a_m(omega) transformed back to time, with e the stored reference in V/(m sqrt(W)) and a the saved amplitudes in the square root of watts. The three components are E_x, E_y, E_z. On a guide whose geometry moves along z, e and the nodes are the guide’s at the plane’s own position, solved again there (the module’s notes).

Parameters:
outputsolprop.output.schema.Output

An opened output handler holding a general modal run.

tablessolprop.translate.modal_tables.ModalTransformTables

The run’s own reference, rebuilt from the guide the record names; its identity is checked against the file’s.

planeint or None, optional

Index of the plane wanted, negative from the end.

zfloat or None, optional

Position of the plane wanted, m. Exactly one of plane and z is given.

analyticbool, optional

Return the complex analytic signal instead of the real physical field. The real field is numpy.real of it, and the two flux accessors differ in exactly this choice.

convention{‘optics’, ‘fft’}, optional

Sign convention of the analytic signal, as every other accessor of solprop.processing takes it and with the same default: the optics convention of docs/dev/conventions.md section 2 unless 'fft' asks for the internal one the reference is solved in. The real field is convention-free, so this is read only with analytic.

guidesolprop.modes.guide.ModalGuide

The guide the reference was rebuilt from. Its sampling and its regions are checked against the file’s own guide identity, which the tables’ identity cannot see. It is required and has no default.

Returns:
numpy.ndarray

The field, shape (n_nodes, 3, n_t), V/m; real unless analytic.

Raises:
solprop.exceptions.OutputError

If the run is not a general modal one, the tables or the guide are not the ones that produced the file, or no single plane was selected.

solprop.processing.modal.instantaneous_flux(output, tables, *, plane=None, z=None, density=False, guide)[source]#

Return the instantaneous Poynting flux of one saved plane, in watts.

(E x H) . z of the real fields, integrated over the cross-section. It carries the sum-frequency term the cycle-averaged reconstructed_flux() drops – the oscillation at twice the carrier – and averages to it over a cycle. Which one a measurement wants is a statement about the detector: an instrument slower than the carrier sees the analytic flux, and a field-resolved measurement sees this one.

The sum-frequency oscillation is aliased when the grid does not resolve it. The trace is formed from products of two real fields sampled on the run’s own time axis, so the term at 2 omega_0 is only representable while 2 omega_0 is below the grid’s Nyquist frequency. The sample values are exact either way – each is the product of two exact samples – and so is the integral identity below; what moves is the apparent frequency of the plotted oscillation, which folds to abs(2 omega_0 - 2 omega_Nyq). A field grid declared for a carrier near the top of its band will therefore show a slow ripple where the physical one is fast, and a plot that means to show the carrier beat needs a window sampled for it.

int inst dt equals int analytic dt when no sum-frequency content folds onto DC: the cross terms are an integer number of cycles in the window and integrate away only while none of them aliases to zero frequency. That holds whenever the spectrum’s support, doubled, stays clear of the sampling frequency’s multiples, which a run whose carrier is inside its declared band satisfies.

Parameters:
outputsolprop.output.schema.Output

An opened output handler holding a general modal run.

tablessolprop.translate.modal_tables.ModalTransformTables

The run’s own reference; its identity is checked against the file’s.

planeint or None, optional

Index of the plane wanted, negative from the end.

zfloat or None, optional

Position of the plane wanted, m. Exactly one of plane and z is given.

densitybool, optional

Return the flux density at each node, (n_nodes, n_t) in W/m^2, instead of its weighted sum over the cross-section.

guidesolprop.modes.guide.ModalGuide

The guide the reference was rebuilt from, checked as in electric_field(). Required.

Returns:
numpy.ndarray

The flux, shape (n_t,) in W, or (n_nodes, n_t) in W/m^2 with density.

Raises:
solprop.exceptions.OutputError

If the run is not a general modal one, the tables or the guide are not the ones that produced the file, or no single plane was selected.

solprop.processing.modal.magnetic_field(output, tables, *, plane=None, z=None, analytic=False, convention='optics', guide)[source]#

Return the magnetic field at the guide’s nodes on one saved plane, in A/m.

The partner of electric_field(), from the stored magnetic reference in A/(m sqrt(W)). The pair is what makes a flux: the provider normalises every channel to unit longitudinal Poynting flux, so int Re(E x conj(H))/2 . z dA is one watt for a unit amplitude and the reconstruction carries SI units throughout.

Parameters:
outputsolprop.output.schema.Output

An opened output handler holding a general modal run.

tablessolprop.translate.modal_tables.ModalTransformTables

The run’s own reference; its identity is checked against the file’s.

planeint or None, optional

Index of the plane wanted, negative from the end.

zfloat or None, optional

Position of the plane wanted, m. Exactly one of plane and z is given.

analyticbool, optional

Return the complex analytic signal instead of the real physical field.

convention{‘optics’, ‘fft’}, optional

Sign convention of the analytic signal, as electric_field() takes it and with the same default.

guidesolprop.modes.guide.ModalGuide

The guide the reference was rebuilt from, checked as in electric_field(). Required.

Returns:
numpy.ndarray

The field, shape (n_nodes, 3, n_t), A/m; real unless analytic.

Raises:
solprop.exceptions.OutputError

If the run is not a general modal one, the tables or the guide are not the ones that produced the file, or no single plane was selected.

solprop.processing.modal.physical_energy(output, *, plane=None, z=None)[source]#

Return the physical energy through a plane, or through every plane, in joules.

solprop.stats.physical_energy(), which is the same-frequency product of the state with the basis’s flux metric integrated over frequency, so a number read off a file and the number the run recorded agree to the bit. It is a scalar per plane because a coordinate energy is not additive on a basis that is not orthonormal. Output schema 7 records the metric’s identity and not its arrays, so this accessor evaluates the unweighted form and refuses by name a record whose modal.metric.kind is not "identity" – the number it could form from the stored state alone is the answer for a different basis. Read the run’s own recorded physical_energy statistic, or reconstructed_flux() here, which reconstructs the physical flux from the guide’s stored reference and needs no metric at all.

Parameters:
outputsolprop.output.schema.Output

An opened output handler holding a general modal run.

planeint or None, optional

Index of the plane wanted, negative from the end; with neither plane nor z, every plane is returned with a leading n_z axis.

zfloat or None, optional

Position of the plane wanted, m.

Returns:
numpy.ndarray

The energy, J, a scalar per plane.

Raises:
solprop.exceptions.OutputError

If the run is not a general modal one. If the recorded basis metric is not the identity, so that the weighted energy cannot be formed from the stored state.

Examples

>>> import numpy as np
>>> from solprop.processing._examples import example_general_output
>>> from solprop.processing.modal import physical_energy
>>> values = physical_energy(example_general_output())
>>> bool(np.allclose(values, values[0], rtol=1e-12))
True
solprop.processing.modal.plane_nodes(output, tables, *, plane=None, z=None, guide)[source]#

Return where one saved plane is reconstructed: its nodes and area weights.

The field accessors of this module return values at the guide’s quadrature nodes and integrate them with its area weights; this returns those nodes and weights, in the same order as the node axis of electric_field(), reconstructed_flux() with density=True and coordinate_intensity(). For a guide whose geometry does not move they are the tables’ own, the same at every plane. For a moving guide they are the plane’s own: the rule that has moved with the interfaces to that plane’s position, which is the provider’s rule asked for there exactly as the entrance rule was (solprop.translate.modal_path.path_rule()) – the same rule the field accessors solve on, so no caller needs the translation layer to plot a plane.

Parameters:
outputsolprop.output.schema.Output

An opened output handler holding a general modal run.

tablessolprop.translate.modal_tables.ModalTransformTables

The run’s own reference; its identity is checked against the file’s.

planeint or None, optional

Index of the plane wanted, negative from the end.

zfloat or None, optional

Position of the plane wanted, m. Exactly one of plane and z is given.

guidesolprop.modes.guide.ModalGuide

The guide the reference was rebuilt from, checked as in electric_field(). Required.

Returns:
tuple of numpy.ndarray

The node positions (x, y) in the cross-section, shape (n_nodes, 2) in m, and their area weights, shape (n_nodes,) in m^2, so that weights @ reconstructed_flux(..., density=True) is reconstructed_flux(...).

Raises:
solprop.exceptions.OutputError

If the run is not a general modal one, the tables or the guide are not the ones that produced the file, no single plane was selected, or a moving guide cannot be solved again at the plane’s position.

Examples

>>> import numpy as np
>>> from solprop.processing._examples import example_general_run
>>> from solprop.processing.modal import plane_nodes, reconstructed_flux
>>> run, tables, guide = example_general_run()
>>> xy, weights = plane_nodes(run, tables, plane=-1, guide=guide)
>>> xy.shape == (weights.size, 2)
True
>>> density = reconstructed_flux(run, tables, plane=-1, density=True, guide=guide)
>>> flux = reconstructed_flux(run, tables, plane=-1, guide=guide)
>>> bool(np.allclose(weights @ density, flux, rtol=1e-12, atol=0.0))
True
solprop.processing.modal.reconstructed_flux(output, tables, *, plane=None, z=None, density=False, guide)[source]#

Return the cycle-averaged Poynting flux of one saved plane, in watts.

Re(E_a x conj(H_a))/2 . z integrated over the cross-section with the guide’s own area weights, from the reconstructed analytic fields. This is the physical power through the plane at each instant, a trace in time, and it is not the trace the run statistic solprop.stats.analytic_flux() is the maximum of (hence the different name): that statistic is a scalar, the peak of the same-frequency diagonal, max_t sum_m |a_m(t)|^2, while this carries the guide’s cross-frequency flux kernel K_ij = sum_q w_q [e_x(w_i) conj(h_y(w_j)) - e_y(w_i) conj(h_x(w_j))]/2 as well. The two peaks differ by at most sum_ij |B_i||B_j||K_ij - 1|, measured at 1.3e-5 of the peak inside a bound of 3.4e-5 on the general-basis test fixture; their time integrals agree, because the cross-frequency terms are an integer number of cycles in the window. This one is the quantity a detector sees.

Negative values are kept. A broadband field’s Hermitian flux can go negative where two channels interfere; clipping it would make the trace look like a power and stop it integrating to the right energy.

Parameters:
outputsolprop.output.schema.Output

An opened output handler holding a general modal run.

tablessolprop.translate.modal_tables.ModalTransformTables

The run’s own reference; its identity is checked against the file’s.

planeint or None, optional

Index of the plane wanted, negative from the end.

zfloat or None, optional

Position of the plane wanted, m. Exactly one of plane and z is given.

densitybool, optional

Return the flux density at each node, (n_nodes, n_t) in W/m^2, instead of its weighted sum over the cross-section.

guidesolprop.modes.guide.ModalGuide

The guide the reference was rebuilt from, checked as in electric_field(). Required.

Returns:
numpy.ndarray

The flux, shape (n_t,) in W, or (n_nodes, n_t) in W/m^2 with density.

Raises:
solprop.exceptions.OutputError

If the run is not a general modal one, the tables or the guide are not the ones that produced the file, or no single plane was selected.