General modal propagation#
This page is for anyone propagating in a solid or graded-index guide whose modes cannot be treated as scalar: a small or high-contrast core, several vector channels, a declared circular basis, or a guide whose geometry moves along z. It sets out the model the general modal route solves, how to run it, what it measures about its own accuracy, and what it refuses. How the route relates to every other transverse decomposition solprop has is set out, with a figure each, in spatial decompositions; the step-by-step workflow, with snippets that run as written, is solid-core fibres.
A waveguide mode is not a scalar. A bound hybrid mode of a solid or a hollow fibre has \(E_x\), \(E_y\) and \(E_z\), and its nonlinear polarisation mixes all three; the transverse scalar profile the rest of solprop propagates against is the weakly guiding limit of that, which is excellent for a large hollow core and progressively worse as the index contrast rises and the core shrinks. The general modal route propagates the full three-component field instead: a provider solves \(\mathbf E\) and \(\mathbf H\) at quadrature nodes across the cross-section, the run synthesises the physical vector field there, evaluates the material’s own response at each node, and projects the result back onto the channels with the reciprocity relation that defines them.
It is reached through the waveguide entry points, with one extra argument:
from_waveguide() and
prop_waveguide() take a provider= in place of a gas
fill. What the route supports, and what it refuses rather than approximates, is set out
in the last two sections of this page; read them before planning a run. In short: the route
runs on a field grid, for a guide of constant geometry or a solid guide whose geometry
moves along z, on numpy_ref, numba_cpu and the native backend (its CPU build and
its CUDA and HIP device builds alike); a coupled flow runs on all of them too, except
that a flow with a CF4 section runs on numpy_ref alone. A general modal run writes,
reads back and resumes an HDF5 file (output schema 7), a scan point writes one, and a
batch of launches on one guide runs in lockstep. A declared basis over a block that is
only nearly degenerate propagates as a coupled matrix flow, and a guide whose geometry
moves carries the connection of its moving basis, which couples its channels.
Declared, refused, recorded is the whole route on one
screen.
Running one#
Three objects, in order: the material regions, the guide, the run. An executed version with four vector channels, and the rule for the band the guide must span, is a step-index fibre, with its vector modes.
import numpy as np
from solprop.driver import prop_waveguide
from solprop.grid import FieldGrid
from solprop.modes import (
IndexMaterial,
MaterialKerr,
MaterialRegion,
ModalGuide,
StepIndexMode,
)
length = 1e-3
grid = FieldGrid.from_limits(length, 835e-9, (790e-9, 890e-9), 200e-15)
# 1. The geometry, and the materials that own the responses. A region with no
# MaterialKerr is linear, which is how a cladding is said to be linear.
core_law, cladding_law = IndexMaterial(1.45), IndexMaterial(1.44)
mode = StepIndexMode(4e-6, core=1.45, cladding=1.44, profile_wavelength=835e-9)
regions = (
MaterialRegion(
"core", 0.0, mode.core_radius, core_law, (MaterialKerr(n2=2.6e-20),)
),
MaterialRegion("cladding", mode.core_radius, None, cladding_law),
)
strand = ModalGuide.from_step_index(
[mode], core=core_law, cladding=cladding_law, regions=regions
).field_provider
# 2. The guide: the provider, solved on its own node rule at its own knots, over
# the run's whole ACTIVE band -- twice the declared one, see below.
active = grid.omega[grid.active_mask]
guide = ModalGuide.from_provider(
strand,
np.linspace(0.99 * active[0], 1.01 * active[-1], 13),
nodes=strand.node_rule(0.99 * float(active[0]), n_r=24),
)
# 3. The run. The guide supplies its own channels, so `modes` is empty.
output = prop_waveguide(
(),
length,
provider=guide,
grid=grid,
lambda0=835e-9,
fwhm=40e-15,
energy=4e-11,
n_save=2,
backend="numpy_ref",
)
The run warns once, with a GeneralModalBasisWarning:
thirteen knots leave the interpolated basis \(1.1\times10^{-4}\) from orthonormal near
1.73 µm, at the long-wavelength edge of the active band where the launched pulse puts no
light. The warning, and why it is a warning rather than a refusal, are explained under
normalisation; more knots remove it.
provider= takes a guide, not a provider. Despite the keyword’s name, what it wants
is a ModalGuide — a field provider together with its
material regions, its ordered channels and the
ModalSampling that records what they were solved on — and
a bare ModalProvider carries none of those.
channel= chooses which channel the pulse enters, by index or by display label
("HE1,1:even"); the default is the first. The launch is the pulse’s amplitude placed
in that channel unchanged: a provider normalises every channel to unit longitudinal
Poynting flux, so a channel amplitude already is a true-power amplitude in \(\sqrt{\rm W}\)
and no \(\sqrt{\operatorname{Re}n_{\rm eff}}\) conversion happens anywhere on this route.
The launched energy is the pulse’s, to the last bit.
incident= launches an incident beam instead. Where channel= names one channel,
incident= takes a SpatialPulse — a Gaussian or super-Gaussian
beam carrying its own temporal pulse — and launches what the guide’s channels capture of
it, coupled under a declared model
(SEP 31;
coupling simulations):
beam = SpatialPulse(GaussianPulse(wavelength=835e-9, fwhm=20e-15, energy=1e-9), 3e-6)
simulation = Simulation.from_waveguide(
(), length, provider=guide, lambda0=835e-9, grid=grid, incident=beam,
coupling=CouplingModel("vector_paraxial", model_error=0.2),
)
simulation.configuration["launch"] # the CouplingReport: incident, launched, budget
The beam’s energy= (or peak_power=) is met over the whole beam in the paraxial
measure \(M_0 = Y_0\,\mathrm dA/2\) of the guide’s core medium, \(Y_0 = n_{\rm ref}(\omega)
\varepsilon_0 c\) with \(n_{\rm ref}\) the real index of the core at each frequency,
integrated over the beam’s own plane in closed form
(incident()) — never over the guide’s quadrature
nodes, which cover only the guide. The beam becomes a radial
FieldPlane that resolves it
(from_pulse() with the core’s index), and
couple() recovers its coordinates in the channels.
The map is the flux pairing, \(\mathbf a = F^{-1}\mathbf B\) with
\(B_m = \int(\mathbf E\times\mathbf h_m^* + \mathbf e_m^*\times\mathbf H)_z\,\mathrm dA/4\)
and \(F\) the same pairing of the channels with each other, on the guide’s nodes. The
beam’s \(\mathbf E\) is its field at the nodes; its \(\mathbf H\) is the explicit forward
closure \(\mathbf H_t = Y_0\hat{\mathbf z}\times\mathbf E_t\) of a paraxial beam in the
core. That closure is not exact for a guided field – a channel’s own \(\mathbf h_t\) is
\(n_{\rm eff}\varepsilon_0c\,\hat{\mathbf z}\times\mathbf e_t\) to leading order, with
corrections where the index changes – so the model admits a guide only where it is
small: the coupling’s admission budget (model_error) bounds the closure’s error
against the provider’s own \(\mathbf H\), the weak-guidance contrast of the guide’s
regions, the angular content of the beam and of the channels, and the metric defect of
\(F\) against the run’s declared metric. A strongly guiding core, such as a silica strand
in air, fails the contrast screen and is refused by name. The pairing also omits the
light the entrance facet reflects; for a step-index guide that omission is bounded by a
recorded envelope against an independent mode-matching solution of the facet, counted
in the budget and refused outside its range (contrast to 0.05, V from 1.5 to 8; see
the coupling chapter).
A graded-index guide has no recorded envelope; its report says so and leaves the item
uncounted. It is not \(Q\), the record’s
other map (modal projection),
which contracts a polarisation for a source term. For a field you sample yourself,
project_transverse_field() applies the
coordinate dual of the channels’ own span, whose E-only closure is exact for a forward
superposition of the channels.
The launch is the recovered amplitudes, unscaled. Light the channels do not capture
is lost at the entrance, not restored: a beam much wider than the core launches a small
share of its energy, and a beam the channels cannot see at all — a y-polarised beam
against a single x-polarised channel — launches nothing, which is a valid zero launch.
The coupling’s CouplingReport is recorded as
configuration["launch"] and reaches the file’s configuration: the incident energy
per frequency bin, the launched energy (the state’s physical energy under the run’s
own declared metric, which is what the run’s physical_energy statistic reads at the
entrance), the admission items and the budget; the launch’s provenance is
configuration["coupling"]. The vector model names no uncaptured energy: \(F\) and the
incident measure are not one positive metric, so their difference is not a physical
loss, and the report says so rather than inventing one. No warning is raised for a poor
capture: it is physics, not a defect.
The beam is launched at its waist (propz=0; otherwise it is refused, because carrying a
beam to the entrance is transport, which this launch does not model) and centred on the
guide’s axis; a one-component Jones vector is polarised along x. The beam carries its own
pulse, so fwhm, width, energy, peak_power, phases, pulses and pulseshape are
refused beside it, as is channel=. Direct amplitudes — a named channel, or a state
passed to with_input() — already describe
light inside the guide and are launched as they are.
Every gas keyword is refused by name beside a provider — gas, pressure,
temperature, kerr, thg, plasma, raman, n_r, n_theta and the rest — because
a guide owns its materials, its responses and its transverse rule, and a keyword
silently ignored would let you believe a fill or a resolution had been applied.
observable_rtol and observable_atol are accepted as None, which means the
solver’s own rtol and atol: the route’s basis is orthonormal in true power, so the
controller’s weighted norm on the amplitudes already is a norm on the physical channel
powers.
For a worked example that runs and plots, see Propagate a vector fibre mode, and check it against the GNLSE; for a graded-index guide, its self-imaging beat and a launch declared as a field, see Self-imaging in a graded-index guide, and a launch declared as a field; for a declared basis of a split pair, propagated as a coupled flow and checked against the provider’s own basis, see A declared basis of a split pair: two bases, one answer. The guide to guided propagation sets out the workflow step by step, and the graded-index worked example runs a multimode fibre at full size.
A guide must be solved over at least twice the band a run declares.
Grid.active_mask runs from half the lowest declared frequency to the window’s highest,
so the FieldGrid above, declared over 790–890 nm, has an active band of
724.9–1774.0 nm — a factor 2.45 in frequency — and the guide’s table must cover all of
it, at the low end of which a weakly bound mode reaches furthest into the cladding and a
truncating node rule discards most of it. Outside the table’s own knots the stored
reference is an explicit zero, and a run that reaches there is refused with both bands
in the message rather than propagated against zeros. An EnvelopeGrid of the same
window has a slightly wider active band, so a guide meant to serve a comparison against
prop_gnlse() has to cover the union of the two.
The collar, and not the route, sets the step on a short grid. from_waveguide
builds the grid’s default temporal collar, applied as a rate inside every stage
(absorbing boundaries). Its rate is set by the
length the grid was declared for and by the time window, and on a short run that rate,
not the nonlinearity, is what the step controller reacts to. On the millimetre above the
general route takes 42 accepted steps with the collar and exactly 10 without, and
prop_capillary on the same grid does the same thing: this is not the general route’s
doing, it only matters more here because a general modal step costs about a hundred
fused ones. Two settings change it: absorber_scheme="split_step" applies the same rates
once per step, outside the stage, and reaches the same answer in the collar-free step
count; absorber=None in the settings removes the absorber, for a lossless check.
absorber=None passed to from_waveguide itself means “build the default collar”, so
replace the settings before the first translate() or run() instead:
import dataclasses
from solprop.driver import Simulation
simulation = Simulation.from_waveguide(
(), length, provider=guide, grid=grid, lambda0=835e-9, fwhm=40e-15, energy=4e-11
)
simulation.settings = dataclasses.replace(
simulation.settings, absorber_scheme="split_step"
)
prop_gnlse builds no absorber at all, so a like-for-like comparison with it needs one
of the two.
Noise, statistics and what comes back#
Because the basis is orthonormal in true power, one photon per channel per bin already
is an energy-normalised amplitude: the shot-noise draw of every other route
(Shot noise) is carried as it stands, with nothing multiplying it by an index, and a seed= reproduces the draw
exactly. In a declared basis the draw is made once, in the orthonormal
true-power reference basis, with the same seed and the same stream, and pushed through
\(U^{\mathsf H}\) (transformed_noise()): the total photon number
in each bin is exactly what it was and the per-channel numbers are redistributed, which
is the point — a fixed-modulus random-phase draw is not invariant in distribution
under unitary mixing, so drawing again in the declared basis would be a different
stochastic model. A basis with unequal channel norms is neither of those and is refused
by name.
The route’s statistics are physical_energy and analytic_flux, and they are
its defaults. Both are scalars: a coordinate energy and a coordinate power are not
additive on a basis that is not orthonormal, so the names that promise one are refused
here rather than redefined. physical_energy is the same-frequency product of the state
with the basis’s flux metric, integrated over frequency; analytic_flux is the peak over
time of that same same-frequency flux, \(\max_t \mathbf a^{\mathsf H}W\mathbf a\). On
an identity metric — every guide in the provider’s own coordinates — that is
\(\max_t\sum_m|a_m(t)|^2\) and their arithmetic is exactly sum(energy(...)) and
peak_power_total, and their docstrings say so.
A third name, spectral_flux, is the same quantity resolved in frequency: the
spectral density \(\mathrm dU/\mathrm d\omega\) of physical_energy, in J/(rad/s), summed
over the channels after the whitening exactly as physical_energy is. It is admitted
but not a default — it is n_omega wide per plane where the other five statistics are
scalars — so ask for it in statistics=; d_omega times its sum is the
physical_energy the same plane records.
analytic_flux is not the cross-section flux of the reconstructed fields. That one
carries the guide’s cross-frequency flux kernel as well, and 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 for one \(\mathrm{HE}_{11}\) channel of a 1.5 µm strand and a 20 fs pulse, while the time integrals agree exactly. For
the flux itself ask solprop.processing.modal.reconstructed_flux(), which
rebuilds E and H and returns the flux as a trace in time; see
statistics.
energy, peak_power, peak_power_total, energy_window, peak_power_window and
peak_intensity are therefore refused by name, each with the replacement to use, at
all four places a statistic can be asked for: the run loop’s statistics admission, the
backends’ stats, the accessors of solprop.processing.modal and the
scalar-route energy() and
peak_power(), which read only the stored record.
omega0, fwhm_t and fwhm_t_outer are admitted, and they take the metric where
the identity no longer holds: they are functionals of the same physical same-frequency flux
\(\mathbf a^{\mathsf H} W\mathbf a\) that analytic_flux peaks, so a run does not report
the width of a quantity whose peak it does not report. zdw takes no metric: it is a
property of the channels’ propagation constants and of no basis at all, and a unitary
transform of an exactly degenerate block leaves every one of them where it was; beside a
split block, which has no per-channel propagation constant, it is withheld (above).
The backend evaluates \(W\) at each plane and hands it over; see statistics. quadrature_error is refused by name too, rather than recorded as
the nan it would be: a provider’s node rule has no embedded coarse rule to difference,
and a nan at every plane would read as “no alarm was raised” when what is true is that
a general modal run has no automatic transverse-resolution alarm — the rule’s tail
bound and its sampled Gram are checked once, when the guide is built, and nothing watches
the resolution during the run.
Output schema 7 carries this basis. A general modal run writes an HDF5 file, reads it
back and resumes it: the record persists the channel labels row by row, the provider, the
sampling the reference is rebuilt from, the materials and their declared Kerr responses,
the table and metric identities with the measured Gram defect, the shared co-moving
frame, the run’s active band, the basis the noise was drawn in, and the list of accessors
the basis admits. All of that is inside the resume key, so a run continued against a
changed guide, table, material or channel order is refused by the field that moved, and a
file written before schema 7 is never resumed into one. A declared change of basis is
in the key four times over — the metric’s kind moves to dense, both gauges gain
+circular(i,j), the channels carry handedness instead of parity, and the sampling
carries the declaration with its matrix — so a file written in one basis cannot resume in
another. The sampling’s entry appears only where a basis is declared, so a run in the
provider’s own coordinates carries no such entry; the two gauges are always compared, and
they refuse a resume in the other direction.
to_file() and a scan point write the same file.
A batch member may be a general modal run, under a narrower admission rule than the
one for fibre runs (Parameter scans) and alongside it: the members of a general-guide group
share the physical guide, the table, the gauge, the materials and one explicit fixed
schedule, and differ in their launch and their noise seed and in nothing else. What
separates two guides is modal_tables.identity — its three declared identities and its
extents — in the grouping signature, beside the metric’s kind and gauge and the compiled
responses’ model and policy; two strands of different radius share every layout entry
of the signature and are told apart by what they physically are. Because a descriptor is only
as good as what it names, a group assembled by hand is refused by name at the driver and
again at the backend: general-guide batch requires identical guide/table/gauge
identity. The source pass is unchanged by batching: the lockstep runner hands each
member its own singleton row, and a batched run is what its members are alone, to
rounding.
A fused batch axis — one problem of shape (n_batch, ...) carrying modal tables — is
refused by the reference backend, because the stage evaluates one member’s
polarisation.
The file stores channel amplitudes, not fields. solprop.processing.modal is the
way back — given the run’s own tables, rebuilt from the guide the record names, it
synthesises \(\mathbf E\) in V/m and \(\mathbf H\) in A/m at the guide’s nodes and forms
reconstructed_flux in time, instantaneous_flux of the real fields with its
sum-frequency term, and coordinate_intensity, the flux density each channel would
carry alone, which is not additive and is never clipped; plane_nodes returns the nodes and area
weights those values live on, which on a moving guide are each plane’s own. A
reconstruction needs the guide beside the file: the arrays are not in it, and guide=
is a required keyword of all six reconstruction accessors, so the sampling and the
regions are always checked too. See output.
Declared, refused, recorded#
This is the whole route in one place, each line pointing at the section that explains it.
What you declare.
declaration |
where |
explained in |
|---|---|---|
the guide: a provider, its material regions and their responses, solved on its own node rule at knots spanning the run’s active band |
|
|
each region’s response: the instantaneous cubic by |
|
|
the cubic’s form |
|
|
the launch |
|
|
a unitary change of basis — of an exactly degenerate block, which stays diagonal, or of a split one, which becomes a coupled flow |
|
|
the statistics |
|
What is refused, and why. Every refusal names itself, at the entry point and again at the translation layer and in the backend.
Every gas keyword beside a provider: the guide owns its materials, its responses and its transverse rule, and a keyword silently ignored would read as applied.
A CF4 section on any backend but
numpy_ref: it has no execution path there (its Gauss-node weights are evaluated inside the reference backend), and nothing falls back.A guide that does not span the active band, or whose sampled or interpolated Gram is further from orthonormal than
gram_atol: the run would propagate against zeros, or against a basis it declares orthonormal and is not.An
n2with noat=whose conversion to \(\chi^{(3)}\) moves by more than one per cent across the band, and the real cubic on a grid with no room for its harmonic.A nonunitary basis, or one acting outside a declared block; and beside a split block, a noise model (its transport through a coupled flow is not measured), a lockstep batch member (it applies neither half of the flow) and a block whose exponent exceeds
MAX_GENERAL_FLOW_EXPONENT.Beside a guide whose geometry moves: a snapshot position other than the entrance, a run longer than the declared path, a noise model, a lockstep batch, a fixed schedule that straddles a kink, and a CF4 flow or no flow at all beside the moving maps (a guide whose geometry moves along z).
The scalar-route power names,
quadrature_errorand — beside a split block —zdw: a statistic the basis does not define is refused with its replacement where it has one, rather than redefined.observable_rtolandobservable_atolas numbers, which nothing would enforce.
How to read the record. output.metadata.modal is the run’s schema-7 modal
identity, and every entry of it is in the key a resume is checked against. basis says
which kind of channels the state carries; guide names the provider, the sampling it
was solved on and — only where one was declared — the change of basis with its matrix;
tables and metric carry the table and metric identities with the measured Gram
departures; channels lists the labels row by row; accessors lists the statistics and
reconstructions the basis admits, which is where a withheld zdw is visibly absent; and
flow appears only where the run carries a coupled flow, with the entries tabulated
under reading the record.
The same leaf elsewhere. The coupled flow a split block becomes is the
linear.coupled_flow leaf the capillary’s coherent wall also becomes, and the
execution is shared: one Schur matrix-function action per stage, with the certificate
checked against the measured action at every step. The wall is documented in
three parts across two chapters: the coherent wall at constant
geometry; a guide that varies along z, where the
capillary’s own connection enters as an explicit residual beside frozen sections; and
the vector boundary response, which couples the
two radial families of one azimuthal order. Each has its gallery example:
The coherent wall of a capillary, against the incoherent one,
Mode conversion and loss in a taper with a coherent wall and
The vector wall: two impedances, two families, one conversion.
What is implemented, and what is not#
Implemented, and executed by every backend — numpy_ref, numba_cpu, and native in its
CPU build and its CUDA and HIP device builds — with the one exception of a CF4 section,
which runs on numpy_ref alone:
Simulation.from_waveguide(..., provider=guide)andprop_waveguide(..., provider=guide), with the launch, the noise draw, the statistics above, in-memory results and an HDF5 file that reads back and resumes (schema 7);the three-component nonlinear source, chunked over nodes with its own memory ledger;
the instantaneous cubic in two declared forms,
kerr_form="real"(the default, third harmonic included) and"analytic", and the silica tensor Raman model as a region’s declared delayed response;a region’s gas fill under the run’s electronic policy, the dispersive cubic of SEP 6 on every backend;
a declared unitary change of basis, with its circular-pair constructor, its dense metric, its relabelled channels, its carried noise draw and its place in the resume key — of an exactly degenerate block, which stays diagonal, and of a split one, which becomes the
linear.coupled_flowleaf described above;the connection of a basis that moves along z, measured from a provider with the flux self-terms kept (
modal_connection()), and the connection a declared moving change of coordinates costs (basis_connection());a moving solid guide: the provider re-solved along its declared radius path, its sectioned synthesis and projection maps, and the frozen-residual coupled flow that carries its connection (a guide whose geometry moves along z);
a graded-index guide as readily as a step-index one: any provider that solves its own E and H, with
GradedIndexProvider(core_responses=…)declaring what its core responds with;a launch into a named channel or a declared transverse field, and a batch of launches on one guide.
The route admits one combination and no other: a field grid; an identity metric
whose certified defect is inside the route’s budget, or the dense metric of a declared
unitary change of basis of one; a constant geometry (one Chebyshev interval of degree
zero along z) or a moving solid guide’s sectioned maps beside the frozen-residual flow
that carries their connection; a state with n_pol = 1; the constant unit power weight;
and each region’s declared responses — the instantaneous cubic in either form, and the
delayed silica tensor Raman model beside it — over the reference’s own three components.
Everything else is refused by name, at the entry point, again at the translation layer
and again in the backend. Every backend admits through one list, the reference’s, run
under its own name, and nothing falls back: a leaf without a kernel is refused by its
dotted name, and backend="auto", which selects native where it is installed, runs a
general guide and a coupled flow there. numba_cpu executes the route through an
execution path of its own — its fused pipeline is written for one real transverse matrix
in the time domain, and this route’s synthesis is a complex frequency-dependent map of
three components — in exactly the combinations numpy_ref admits, and agrees with it to
rounding (a few 1e-16 after ten steps); native agrees with numpy_ref to a derived
rounding bound. See backends and the native backend.
Some refusals are permanent and not a gap to be filled. peak_intensity, like
energy and peak_power, is refused because a general basis does not define it, and
solprop never redefines an established name on a basis it does not describe: the
replacement is analytic_flux, or reconstructed E and H with an explicit detector
definition. The same holds for every scalar-route power name above.
Not implemented, and refused rather than approximated:
quadrature_error, refused by name rather than recorded as anan, because a provider’s node rule carries no embedded coarse rule to difference. Unlike the power names this one is a gap: a second rule, or a tail estimate, would give the alarm something to report;the reference arrays in the file. Output schema 7 records the guide’s identity and not its tens of megabytes of stored E and H, so a reconstruction is made by rebuilding the tables from the guide — tens of milliseconds for the route, about 0.7 s if the guide itself must still be solved — and every accessor checks them against the identity the file carries, path by path, with the guide, which they require;
observable_rtolandobservable_atolas numbers.Noneis accepted and means the solver’s ownrtolandatol, which on this route is a true statement; a different number is refused by name, because the separate error allocation it asks for — over the table’s frequency interpolation, the transverse rule and the step — is not implemented, and accepting a number nothing enforces would be a silent claim about accuracy;a backward or evanescent declared launch.
incident=(above) recovers the incident field through the flux pairing with the forward-field closure, and nothing in the arithmetic can see that a declared field was not forward-propagating; such a launch is built by hand and passed towith_input();a nonorthogonal basis:
modal_metricof kind"diagonal", which is a power-orthogonal basis with unequal channel norms and which nothing here builds, and any"dense"metric that is not the unitary change of basis of a certified orthonormal reference described above. A nonunitary transform leaves a skewed or leaky basis whose flow is a matrix exponential and whose noise transport nothing has measured, and it is refused at three layers by name;a guide sampled over a support narrower than the run’s active band. An opt-in fallback — the field zero-filled outside the support, the run warning by name when energy reaches its edge — would suit guides whose every solve is expensive, such as finite-element tables, but it is not built: the linear operator asks every channel for a propagation constant across the whole active band, through the one site every route in the package shares. A guide that does not span the active band is therefore refused, with both bands in the message;
a CF4 section on the compiled backends.
numpy_refevaluates a CF4 step’s Gauss-node weights itself, andnumba_cpuandnativehave no path for them.
Two approximations are worth naming because a reader would otherwise take them as exact. Outside the guide’s sampled band the stored reference is an explicit zero rather than an extrapolated profile — which is why the table must span the whole active band, twice the declared one or more, as above. And on a constant guide the node rule is frozen at the entrance geometry (a moving solid guide’s rule moves with its interfaces, section by section): the rule’s certified tail bound and its sampled Gram belong to that geometry and to no other.
The incident-beam launch (incident= under coupling=) and its coupling budget were
proposed and carried out by SEP 31.