solprop.translate.general#

The general modal route: the seam leaves of one guide, and their certificates.

A general modal guide reaches a backend as three of the four optional leaves kernel schema 12 added – the E/H reference and its execution maps (transform.modal_tables), the basis metric (linear.modal_metric) and the per-region response (responses.regional_responses) – plus the ordinary diagonal linear operator every modal route has. GeneralModalRoute is what solprop.translate.problem.assemble() is handed: the leaves that were built together, with the certificate that binds them.

The fourth leaf, linear.coupled_flow, is filled for exactly one case: a declared unitary basis of a split block, where U.H diag(beta) U is not diagonal and the channels genuinely beat. The route carries the transformed generator relative to one reference channel (coupled_block()) and solprop.translate.problem.assemble() turns it into the leaf with its certificate, because the largest step the flow’s conditioning is declared over is the assembly’s to know. A guide in the provider’s own coordinates, and a declared basis of an exactly degenerate block, leave it None and propagate through the diagonal flow alone.

Module Attributes

MAX_DENSE_METRIC_DEPARTURE

Largest norm(W(omega) - I, 2) a dense metric may show and still be executed.

N2_CONVERSION_RTOL

Spread of a converted chi3 this route will accept at an undeclared frequency.

Functions

check_noise_basis(metric, *, has_noise)

Refuse a shot-noise draw in a basis it is not defined in.

coupled_block(table, grid, *[, reference])

Return a table's coupled generator relative to one channel, or None.

general_modal_route(guide, grid, *, z_max, ...)

Build one guide's seam leaves on the grids a run will execute on.

guide_identity(guide)

Return what a guide is, as a run records it and a resume compares it.

interpolated_gram(table, tables)

Sample the reference's flux Gram at the run's own coarse bins.

metric_departure(sampled, *[, occupied, weight])

Return how far a sampled metric is from the identity, over chosen bins.

modal_identity(route, grid, *, channels, ...)

Return the general modal identity output schema 7 persists.

occupancy_weight(grid, launch)

Return which bins a run occupies, and how heavily, for the basis gate.

Classes

GeneralModalRoute(tables, responses, gram_defect)

Own the leaves of one general modal guide and the certificate that binds them.

class solprop.translate.general.GeneralModalRoute(tables, responses, gram_defect, certificate_id='sampled-gram', occupied_departure=0.0, active_departure=0.0, gram_atol=0.0001, guide_identity=None, metric_tables=None, coupled_generator=None, reference_channel=0, moving_generator=None, moving_connection=None, moving_error=0.0, connection_certificate=0.0, dispersive=None, electronic=())[source]#

Bases: Displayed

Own the leaves of one general modal guide and the certificate that binds them.

The record a run is assembled from. It is not itself a kernel-contract field: solprop.translate.problem.assemble() takes it apart into the three leaves of the groups they belong to, and the metric it derives from the declared Gram defect rather than storing a second copy of the same certificate.

The metric is the identity unless a basis is declared. A guide built in the provider’s own coordinates is a Poynting-normalised orthogonal basis whose sampled Gram passed the construction gate of solprop.translate.modal_tables.DenseModalTable, so W = I to within gram_defect and the route declares the identity. A guide that declares a unitary change of basis (solprop.modes.guide.ChannelBasis) carries the dense metric metric_tables instead: W' = U.H W U is the identity in exact arithmetic and is not the identity in the measured Gram the run actually has, so it is stored and declared rather than decorated. Either way the statistics power weight is the constant one: the Poynting amplitude is the true-power amplitude and the legacy sqrt(Re n_eff) boundary is not crossed. The two leaves are different objects and both exist: linear.power_weight is the statistics boundary and linear.modal_metric is the basis Gram.

Attributes:
tablessolprop.translate.modal_tables.ModalTransformTables

The E/H reference and the two execution maps, tabulated on the run’s own canonical grids and zero outside the guide’s support.

responsessolprop.translate.responses.RegionalResponseTables or None

The compiled response of each material region, or None for a guide whose materials are all linear – a propagation with no source at all, which is the linear reference case.

gram_defectfloat

The largest measured norm(W - I, 2) of the basis the tables were sampled from, dimensionless and non-negative; the table’s own gram_defect. It is the error row of the identity metric.

certificate_idstr

Non-empty identity of the certificate gram_defect came from.

occupied_departurefloat

The interpolated norm(W - I, 2) over the bins this run occupies, dimensionless and non-negative. gram_defect is measured at the table’s knots; this one is measured at the run’s own coarse bins, between them, where a sampled basis is further from orthonormal – three orders further on the weakly guiding fixture (6.97e-8 against 1.14e-4). Declaring W = I costs a run that much in solprop.stats.physical_energy() to first order, so it is gated against gram_atol like the knot defect. Where the light is, not where the knots are sparsest: see occupancy_weight(). Zero on a record assembled by hand, which measures nothing and claims nothing.

active_departurefloat

The same interpolated norm(W - I, 2), maximised over every bin grid.active_mask admits and weighted by nothing, dimensionless and non-negative. occupied_departure discounts a bin by how little of the launched spectrum sits in it, which certifies the launch; this one certifies the run, because a broadening run can reach any active bin. It is warned on rather than gated (solprop.exceptions.GeneralModalBasisWarning), because a refusal would drop guides whose light never goes there and only the caller knows whether it will. Zero on a record assembled by hand.

gram_atolfloat

Largest defect this route will call the identity, dimensionless and positive; solprop.translate.modal_tables.DEFAULT_GRAM_ATOL by default, which is the budget DenseModalTable gates its own sampling with. A defect above it is refused here rather than propagated: the route is the statement W = I, and on a continuous wave the resulting error in da/dz is |1/W_00 - 1|, which is the defect itself to first order. A certificate that is merely recorded and never gated is an unbounded silent error.

guide_identitycollections.abc.Mapping or None

What the guide this route was built from is, as guide_identity() writes it: the provider’s own identity, the sampling the reference is rebuilt from, and each material region with its declared Kerr response. It is carried here because the route is the last object that sees the guide – solprop.translate.problem.assemble() takes the leaves apart and the guide never reaches a KernelProblem – and output schema 7 persists it inside solprop.output.schema.RunMetadata.modal. None for a record built by hand, which modal_identity() then refuses by name rather than writing a file that cannot say what produced it.

coupled_generatornumpy.ndarray or None

The internal-convention generator of a declared split block, relative to reference_channel, in 1/m, complex, shape (n_modes_coupled_out, n_modes_coupled_in, n_omega_coupled), exactly zero outside the run’s active band (coupled_block()). None – the ordinary case – whenever the generator is diagonal, and then the assembled problem carries no linear.coupled_flow and propagates through the diagonal flow alone. It is not the leaf: the leaf’s conditioning certificate is declared over the largest step the run may take, which this record does not know.

reference_channelint

Which channel the cumulative Phi beside a coupled block is built from, and therefore what coupled_generator is measured relative to. Zero, the first channel, as the coherent wall’s is; a coupled block needs a Phi that is a multiple of the identity in the channel axis, so one channel’s dispersion is carried there and every channel’s departure from it in the block.

moving_generator, moving_connectionnumpy.ndarray or None

A moving guide’s flow: the Chebyshev coefficients along z, on the tables’ own sections, of the internal-convention generator less the reference channel’s entrance diagonal, and of the connection Gamma = W^-1 S in the same amplitude, both in 1/m, complex, shape (n_intervals, n_deg, n_modes, n_modes, n_omega), zero outside the active band. Supplied together or not at all; None for a snapshot, a guide that does not move.

moving_errorfloat

The relative Chebyshev tail of those expansions, or the path’s held-out error, whichever is larger, dimensionless; zero with no moving flow.

connection_certificatefloat

The worst relative antisymmetry certificate of the measured connection, norm(W Gamma + Gamma^H W - W', 2) / max|Gamma|, dimensionless; zero with no moving flow.

dispersivesolprop.translate.responses.DispersiveKerrTerms or None

The dispersive channels of the guide’s gas regions under one of SEP 6’s dispersive electronic policies (solprop.modes.material.GasKerr), each with its node region, or None. Under a carrier policy a gas region is a constant cubic in responses instead.

electronictuple of solprop.nonlinear.responses.DispersiveKerrResponse

The responses dispersive was sampled from, one per channel, each labelled "<region>:<population>": what the run’s material manifest records. Empty without dispersive channels.

metric_tablessolprop.translate.coupled.ModalMetricTables or None

The dense metric of a guide that declares a unitary change of basis, built from the reference’s own flux Gram on the run’s coarse grid (solprop.translate.coupled.dense_metric()), or None for a guide in the provider’s own coordinates, whose metric is the identity. Where it is present its gauge_id is the tables’ composed one and its grids are the tables’ own, both checked here: a metric written on another basis or another grid than the reference it describes is not a metric of this run.

Raises:
TranslationError

If the defect is negative, not finite or above gram_atol, the budget is not positive and finite, the certificate is empty, the tables carry no execution map, or a supplied metric_tables is not a dense metric of these tables’ own basis and grids.

active_departure: float = 0.0#
certificate_id: str = 'sampled-gram'#
connection_certificate: float = 0.0#
coupled_generator: Complex[ndarray, 'n_modes_coupled_out n_modes_coupled_in n_omega_coupled'] | None = None#
dispersive: DispersiveKerrTerms | None = None#
electronic: tuple[DispersiveKerrResponse, ...] = ()#
gram_atol: float = 0.0001#
gram_defect: float#
guide_identity: Mapping[str, JsonValue] | None = None#
property metric: ModalMetricTables[source]#

Return the basis metric this route declares.

Returns:
solprop.translate.coupled.ModalMetricTables

The declared dense metric where a change of basis was declared, and otherwise the identity metric on the tables’ own grids, carrying the measured Gram defect as its error row.

metric_tables: ModalMetricTables | None = None#
property moving: bool[source]#

Return whether the route is a moving guide’s.

Returns:
bool

True when the route carries a moving flow, whose maps then move too.

moving_connection: Complex[ndarray, 'n_intervals_link n_deg_link n_modes_link_out n_modes_link_in n_omega_link'] | None = None#
moving_error: float = 0.0#
moving_generator: Complex[ndarray, 'n_intervals_moving n_deg_moving n_modes_moving_out n_modes_moving_in n_omega_moving'] | None = None#
property n_modes: int[source]#

Return how many channels the guide propagates.

Returns:
int

The channel axis of the stored reference.

occupied_departure: float = 0.0#
reference_channel: int = 0#
responses: RegionalResponseTables | None#
tables: ModalTransformTables#
solprop.translate.general.MAX_DENSE_METRIC_DEPARTURE: Final[float] = 0.001#

Largest norm(W(omega) - I, 2) a dense metric may show and still be executed.

Dimensionless. The general route propagates through the diagonal cumulative flow and measures the step controller’s norm on the coordinate amplitudes, both of which are the basis’s own quantities only where that basis is orthonormal in true power. A declared unitary change of basis of a certified reference stays there – the spectral norm is unitarily invariant, so norm(U.H W U - I, 2) = norm(W - I, 2) exactly – and a nonunitary transform does not.

The number. The quantity is measured on the run’s own coarse grid, between the table’s knots, where an interpolated basis is further from orthonormal than at the knots solprop.translate.modal_tables.DEFAULT_GRAM_ATOL gates: the weakly guiding fixture of tests/driver/test_general_modal.py, whose knot defect is 6.97e-8, reaches 1.136e-4 there at 13 knots – three orders apart. So this budget cannot be the knot budget.

What sets the margin is the departure over the *occupied* support, and it is smaller than the headline. The interpolated defect lives at the long-wavelength end of the sampled band, far outside where a run puts any light: on that fixture the worst bins are 1.729 um at 1.136e-4 and 1.685 um at 9.90e-5, while inside the 790–890 nm launch window the departure is 2.32e-7, so the latent error in a launched 835 nm 40 fs pulse’s physical_energy is 3.70e-8 weighted by its own spectrum. That occupied quantity is what GeneralModalRoute.occupied_departure measures and what gram_atol gates, on the identity route as much as on this one; this constant stays the whole-band budget, because what it exists to catch – a nonunitary transform – is nonunitary in every bin and not only where the light is. The headline number is also sampling-dependent rather than a property of the provider: at that fixture builder’s default nine knots the same block measures 4.03e-4, only 2.5 times under this budget, and the 1.5 um strand fixture measures 2.46e-5 at nine. So 1e-3 is not comfortably an order above the loosest departure a provider here produces; it is a factor of a few above a poorly sampled one and three orders below the order-one departure a nonunitary transform produces, which is the departure it exists to catch. It is the loosest of the three gates a declared basis passes – the declaration’s own 1e-12 unitarity budget (solprop.modes.guide.BASIS_UNITARY_ATOL), which the per-knot invariance check of solprop.translate.modal_tables.apply_channel_basis() takes as its own floor, is the tight one – and it is the one a backend can apply, since it sees the stored block and not the declaration.

solprop.translate.general.N2_CONVERSION_RTOL: Final[float] = 0.01#

Spread of a converted chi3 this route will accept at an undeclared frequency.

Relative and dimensionless: the largest max/min - 1 of the executed chi3 across the run’s active band that this route will convert at a frequency nobody declared.

A material region’s compiled response carries one susceptibility, so a declared n2 becomes a chi3 at one frequency (chi3 = 4 eps0 c n_0^2 n2 / 3) and the choice of frequency multiplies it by the square of an index ratio.

One per cent is a policy number, not a derived one. It is chosen to sit comfortably below the case that motivated it – fused silica spans 2.13 % of its own chi3 between 800 nm and 2 um – with enough margin that a material like it is caught whatever band a run declares, while a noble gas below a few bar, which spans less than 0.1 % across any band, is never asked. The asymmetry that makes a low threshold cheap is that saying where costs the caller one keyword, MaterialKerr(n2=..., at=<m>), while an undeclared conversion is unrecoverable: the leaf carries one number and nothing downstream can say which frequency it came from, so the conversion is refused rather than guessed.

solprop.translate.general.check_noise_basis(metric, *, has_noise)[source]#

Refuse a shot-noise draw in a basis it is not defined in.

The existing draw is one photon per channel per bin with a random phase, and that is a statement about an orthonormal true-power basis: only there does “one photon in this channel” mean an energy of hbar omega. Two metrics satisfy that and are admitted:

  • "identity" – the provider’s own Poynting-normalised coordinates, where the draw is added to the state as it stands;

  • "dense" – a declared unitary change of basis of such a reference, where the same draw is made once in the reference basis, with the same seed and the same stream, and carried into the run’s coordinates by solprop.noise.transformed_noise(). The total photon number per bin is exactly what it was and the per-channel numbers are redistributed, because fixed-modulus random phases are not invariant in distribution under unitary mixing. That the dense metric really is such a transform is gated where it is built – unitarity and the block’s exact degeneracy at the table, the departure from the identity at the route and again at the backend – so by the time it reaches here it is one.

A "diagonal" metric is refused: it is a power-orthogonal basis with unequal channel norms, its per-channel energies are not hbar omega, nothing in this package builds one and no transport for it has been derived.

Parameters:
metricsolprop.translate.coupled.ModalMetricTables or None

The basis metric the route declares, or None for a route with none.

has_noisebool

Whether the run carries a shot-noise realisation.

Raises:
TranslationError

If a realisation is carried and the metric is neither the identity nor the dense metric of a declared unitary change of basis.

solprop.translate.general.coupled_block(table, grid, *, reference=0)[source]#

Return a table’s coupled generator relative to one channel, or None.

A guide whose channels do not exchange power has a diagonal generator at every bin, and then each channel’s dispersion goes into the cumulative diagonal flow and this returns None: the ordinary route, untouched. A declared unitary basis of a split block does not, because U.H diag(beta) U is diagonal only where the block’s propagation constants are equal (solprop.translate.modal_tables.apply_channel_basis()), and then what the run propagates is a matrix flow.

What is returned is the whole generator less reference’s own diagonal entry, because that is what the cumulative Phi beside a coupled block carries: the block’s exponential and the diagonal one multiply out to the same equation only where Phi is a multiple of the identity in the channel axis, so one channel’s dispersion is carried there and every channel’s departure from it here, diagonal included. The co-moving frame cancels between the two for the same reason.

It is exactly zero outside grid.active_mask, as every leaf of the contract is: the state is masked to zero there and the table is not defined at every one of those bins.

Parameters:
tablesolprop.translate.modal_tables.DenseModalTable

The tracked reference, in whatever basis was declared on it.

gridsolprop.grid.TimeGrid

The run’s field grid, whose coarse bins the block is tabulated on.

referenceint, optional

Which channel’s dispersion the diagonal flow carries; the first by default, as the coherent wall’s is.

Returns:
numpy.ndarray or None

The block in 1/m, complex, shape (n_modes, n_modes, n_omega), or None when the generator is diagonal to the rounding of its own propagation constants at every bin.

Raises:
TranslationError

If reference is not one of the table’s channels.

Examples

>>> import numpy as np
>>> from solprop.grid import FieldGrid
>>> from solprop.translate.general import coupled_block
>>> from solprop.translate.modal_tables import sample_modal_table
>>> from solprop.modes.guide import ModalGuide
>>> from solprop.modes.stepindex import StepIndexMode
>>> from solprop.modes.material import IndexMaterial, MaterialRegion
>>> modes = [
...     StepIndexMode(3e-6, core=1.45, cladding=1.4, profile_wavelength=835e-9,
...                   kind=kind, n=0, m=1, parity="even")
...     for kind in ("TE", "TM")
... ]
>>> core, cladding = IndexMaterial(1.45), IndexMaterial(1.4)
>>> guide = ModalGuide.from_step_index(
...     modes, core=core, cladding=cladding,
...     regions=(
...         MaterialRegion("core", 0.0, 3e-6, core),
...         MaterialRegion("cladding", 3e-6, None, cladding),
...     ),
... )
>>> grid = FieldGrid.from_limits(1e-3, 835e-9, (800e-9, 870e-9), 60e-15)
>>> active = grid.omega[grid.active_mask]
>>> omega = np.linspace(float(active[0]), float(active[-1]), 4)
>>> provider = guide.field_provider
>>> table = sample_modal_table(
...     provider, omega, np.array([0.0]),
...     provider.node_rule(float(active[0]), n_r=8), gram_atol=1.0,
... )
>>> coupled_block(table, grid) is None
True
solprop.translate.general.general_modal_route(guide, grid, *, z_max, omega_reference, kerr_form='real', launch=None, margin=0.05, gram_atol=0.0001, selection=None)[source]#

Build one guide’s seam leaves on the grids a run will execute on.

The bridge between a solprop.modes.guide.ModalGuide – physics, owned by the modes package – and the three schema-12 leaves a backend executes. The guide’s own declared sampling (solprop.modes.guide.ModalSampling) is resolved into a dense reference table, the table is evaluated onto the run’s canonical coarse and fine grids, and each material region’s declared response is compiled into the per-region leaf. Nothing here invents a resolution: the knots, the node rule and the degenerate blocks are the guide’s.

The guide must span the run’s active band. Grid.active_mask starts at half the lowest declared frequency, so a run declared over 815–860 nm has an active band of about 756–1650 nm – a factor of 2.19 in frequency – and the cumulative operator samples every channel’s propagation constant across all of it. Outside the guide’s knots the stored reference is an explicit zero, so a run reaching there would be propagated against zeros; it is refused here instead, with the measured numbers in the message.

The basis is certified where the run occupies it. The table’s construction gate measures the Gram at the knots; this route also measures it at the run’s own coarse bins, between them, over the bins the run occupies, and refuses a basis that is further from orthonormal there than gram_atol. That interpolated departure is three orders above the knot defect on a sparsely sampled guide and is the number a run actually propagates against; it is an interpolation error and falls with the knot spacing. Which bins count is occupancy_weight()’s: launch supplies the weighting where it is known.

Parameters:
guidesolprop.modes.guide.ModalGuide

A guide built by solprop.modes.guide.ModalGuide.from_provider(), so that it carries the sampling its reference is rebuilt from.

gridsolprop.grid.TimeGrid

The run’s field grid, which supplies the canonical coarse and fine frequency axes, the active band and the fine temporal quadrature weight.

z_maxfloat

Length the run covers, m, positive and finite; the tables are tabulated over [0, z_max (1 + margin)] as every other route’s are.

omega_referencefloat

Positive angular frequency in rad/s at which a region’s declared n2 is converted to an absolute chi3 – the run’s own reference frequency. A region that declares chi3 ignores it.

kerr_form{‘real’, ‘analytic’}, optional

Which instantaneous cubic the regions’ Kerr responses are. "real", the default, is the real-field cubic P = eps0 chi3 (E.E) E the legacy field route evaluates, third harmonic included; "analytic" is the analytic-signal form, which carries the fundamental band alone and is the tested oracle for it.

launchnumpy.ndarray or None, optional

The launched spectrum, shape (n_modes, n_pol, n_omega), on this grid’s own frequency axis. It weights the occupied-support basis gate by where the light actually is; None, the default, falls back to the run’s declared band (occupancy_weight()). It is used for that certificate and for nothing else – no leaf carries it.

marginfloat, optional

Fraction of z_max the tables extend beyond the end, matching the linear operator’s.

gram_atolfloat, optional

Largest departure from orthonormal this route calls the identity, passed both to the table’s construction gate and to the record.

selectionsolprop.physdata.selection.MaterialSelection or None, optional

The run’s material selection, whose electronic policy a region’s gas fill (GasKerr) takes: the Kerr coefficient at the carrier 2 pi c / omega_reference, or SEP 6’s dispersive cubic, with kerr_form="real" its full cubic and "analytic" its number-conserving sector. None is MaterialSelection(). A region’s declared MaterialKerr is constant under every policy.

Returns:
GeneralModalRoute

The tables, the compiled per-region responses (None for a guide whose regions are all linear) and the measured Gram certificate.

Raises:
TranslationError

If the guide declares no sampling, the run is not on a field grid, the guide’s band does not cover the run’s active band, the interpolated basis is further from orthonormal than gram_atol over the bins the run occupies, the real cubic is asked for on a grid that cannot hold its third harmonic, a region declares an n2 with no measurement wavelength where its own index moves the converted chi3 by more than N2_CONVERSION_RTOL across the active band, a region declares one outside its own index model, or the assembled record does not validate.

solprop.translate.general.guide_identity(guide)[source]#

Return what a guide is, as a run records it and a resume compares it.

The provider’s own identity – its model, its ordered channels and its materials – together with the two things the provider protocol does not declare and a run depends on: the solprop.modes.guide.ModalSampling the dense reference is rebuilt from, and each material region with the Kerr response it declares. Every entry is a scalar, a string or a list of them, so that the mapping survives JSON unchanged and two processes agree on it.

Parameters:
guidesolprop.modes.guide.ModalGuide

A guide carrying a sampling, as from_provider() builds it.

Returns:
collections.abc.Mapping

JSON-serialisable identity.

Raises:
TranslationError

If the guide declares no sampling, so that nothing says what resolution its reference was built at.

solprop.translate.general.interpolated_gram(table, tables)[source]#

Sample the reference’s flux Gram at the run’s own coarse bins.

The same interpolated samples the reference arrays were built from, one per coarse bin inside the guide’s band, so this block and the reference are the same object seen twice and cannot drift apart. It is what both the declared dense metric and the identity route’s occupied-support gate are measured on.

Outside the band the block is the identity, not zero and not the last knot extrapolated. The stored reference is an explicit zero there (solprop.translate.modal_tables.modal_transform_tables()) and a run whose active band leaves the guide’s support is refused where the problem is assembled, so no state ever lives in those bins; what the record must not do is declare a singular metric there, because a zero block has a zero smallest eigenvalue, which is the record’s own way of saying “certifies no positive error or noise norm”. The identity is the honest statement that those bins carry no basis information and nothing lives in them.

Parameters:
tablesolprop.translate.modal_tables.DenseModalTable

The tracked reference, in whatever basis it was built in.

tablessolprop.translate.modal_tables.ModalTransformTables

The flat record, whose coarse grid the block is sampled on.

Returns:
numpy.ndarray

W(omega), complex128, shape (n_modes, n_modes, n_omega), dimensionless.

solprop.translate.general.metric_departure(sampled, *, occupied=None, weight=None)[source]#

Return how far a sampled metric is from the identity, over chosen bins.

The one measure of how far a basis is from orthonormal in true power, shared by the route that builds a dense metric and by the backend that admits one, so that the declared bound and the executed check are the same quantity.

By default it is max_omega norm(W(omega) - I, 2) over every bin, which is the right question for a declared change of basis: a nonunitary transform is nonunitary wherever it is applied. It is the wrong question for the interpolation error a sampled table has at the run’s own bins, because that error lives where the table’s knots are sparsest and a run need put no light there at all. occupied narrows the bins looked at and weight replaces the maximum by a weighted mean, so that a route can ask instead what the departure costs where its light is – which, to first order, is exactly the relative error an identity metric puts into solprop.stats.physical_energy().

Parameters:
samplednumpy.ndarray

The Hermitian metric per frequency, shape (n_modes, n_modes, n_omega).

occupiednumpy.ndarray or None, optional

Boolean mask over the frequency axis selecting the bins to look at; None, the default, looks at every bin.

weightnumpy.ndarray or None, optional

Non-negative weights over the frequency axis, of any scale. Where given, the result is sum_k w_k d_k / sum_k w_k over the occupied bins instead of max_k d_k; None, the default, takes the maximum.

Returns:
float

Non-negative and dimensionless.

Raises:
TranslationError

If occupied or weight does not cover the frequency axis, a weight is negative or not finite, occupied selects no bin, or the selected weights sum to zero, so that there is no occupied support to measure over.

Examples

>>> import numpy as np
>>> from solprop.translate.general import metric_departure
>>> block = np.eye(2, dtype=complex)[:, :, None] * np.ones(3)
>>> float(metric_departure(block))
0.0
>>> block[0, 0, 2] = 1.5
>>> float(metric_departure(block))
0.5
>>> float(metric_departure(block, occupied=np.array([True, True, False])))
0.0
solprop.translate.general.modal_identity(route, grid, *, channels, has_noise, flow=None)[source]#

Return the general modal identity output schema 7 persists.

One mapping answering, for a file read years later, every question the mode axis of a general modal run raises: which channels, in which order, with which quantum numbers; which tables, by the three declared identities and the extents of the stored reference; which basis metric and how far from orthonormal it was measured to be; which responses, by model and by the declared susceptibility of each region; which guide, by provider, sampling and materials; the shared co-moving frame; the run’s active band; the basis the noise was drawn in; and which accessors the basis admits (solprop.stats.GENERAL_MODAL_ACCESSORS, which solprop.output.capability gates with).

It goes into solprop.output.schema.RunMetadata.modal, which is inside the schema-7 resume key, so a run continued against a changed guide, table, material or channel order is refused by the field that changed.

A declared change of basis is in the key by four independent paths, so a file written in one basis cannot resume in another: metric.kind moves from "identity" to "dense"; metric.gauge_id and tables.gauge_id both carry the composed identity "<tracking>+<basis>"; the channels rows carry the handedness and lose the parity; and guide.sampling.basis carries the declaration itself, matrix and all. The last of those is present only where a basis is declared, so the key of a guide in the provider’s own coordinates does not carry it; the first three are always compared, which is what makes that omission safe in both directions.

Measured entries are quantised and declared ones are not. The metric’s Gram defect, the sampling’s profile error and the tables’ profile-error budget are the outputs of a dense solve and a reduction, so they enter at MEASURED_DIGITS significant digits: a re-translation of the same guide on another BLAS moves their last bits and must not refuse a resume. A declared chi3, a radius, a tolerance and a knot enter exactly, because a caller who changes one means to.

Parameters:
routeGeneralModalRoute

The route the run was assembled from, carrying its guide_identity.

gridsolprop.grid.TimeGrid

The run’s grid, whose active band the identity records.

channelscollections.abc.Sequence

The ordered channel labels of the mode axis, one per row.

has_noisebool

Whether the run carries a noise realisation, which fixes the reference basis the noise draw was made in.

flowsolprop.translate.coupled.CoupledFlowTables or None, optional

The linear.coupled_flow the assembled problem carries, when it carries one. Its description joins the record under "flow", as solprop.translate.coupled.wall_identity()’s does, so that a resume across a changed basis is refused by the block that moved as well as by the gauge. The key is written only where a flow exists, so the key of a run without one does not carry it, exactly as guide.sampling.basis is written only where a basis is declared.

Returns:
collections.abc.Mapping

JSON-serialisable identity, stable across processes.

Raises:
TranslationError

If the route carries no guide identity, or the labels do not match the table’s channel count.

solprop.translate.general.occupancy_weight(grid, launch)[source]#

Return which bins a run occupies, and how heavily, for the basis gate.

Two statements, in order of strength. The bins a run can occupy are grid.active_mask: the linear operator is identically zero outside it and the hard band mask zeroes the state there once per accepted step, so a bin outside it is a bin nothing ever occupies. Where a launch is declared the weighting is that launched spectral energy density, which makes the weighted departure the first-order relative error an identity metric costs the launch’s solprop.stats.physical_energy().

The weighting is the launch and not the run. Nothing here knows what the run does with it: a broadening run occupies bins the launched spectrum discounted to nothing, and pays the basis error there in full. That is why general_modal_route() measures the unweighted maximum over grid.active_mask as well, and warns when it leaves the budget (solprop.exceptions.GeneralModalBasisWarning, GeneralModalRoute.active_departure).

Where no launch is declared – a record assembled by hand, or a guide checked before a pulse exists – the fall-back is the run’s declared band, grid.inband_mask, unweighted: the only statement about occupancy a grid makes on its own. It is the conservative one of the two, since a maximum over a band is never below a mean weighted inside it.

Parameters:
gridsolprop.grid.TimeGrid

The run’s grid, whose active and in-band masks say where light can be.

launchnumpy.ndarray or None

The launched spectrum, shape (n_modes, n_pol, n_omega), internal convention, or None.

Returns:
tuple

The occupied mask and the weights, the latter None where no launch was declared, for metric_departure().

Raises:
TranslationError

If launch does not lie on this grid’s frequency axis, or carries no energy inside the active band at all.