solprop.translate.modal_tables#
Track and interpolate dense Maxwell tables without changing the kernel seam.
Module Attributes
Largest sampled Gram defect |
|
Smallest aligned physical overlap a tracked channel may keep. |
|
Largest off-diagonal generator a per-channel index table admits, relatively. |
|
Fewest longitudinal knots a table interpolates over, beside a single snapshot. |
Functions
|
Align channels by physical overlap, phase and declared subspaces. |
|
Put one tracked sample in a declared channel basis, splitting the two cases. |
|
Return the scalar effective area of one channel of a vector sample. |
|
Return the gauge a declared basis leaves on top of a tracking gauge. |
|
Return whether a tracked table's generator couples its channels anywhere. |
|
Build the flat seam record from a dense reference table. |
|
Project a declared transverse electric field onto a guide's channels. |
|
Build dense knots with frequency and longitudinal physical-overlap tracking. |
|
Sample one channel of a modal provider into a dense index snapshot. |
|
Solve a fresh sample and put it in the table's declared channel basis. |
|
Put a sample in new channel coordinates: |
|
Compare dense interpolation with held-out physical provider solves. |
Classes
|
Retain full reference samples at every frequency and longitudinal knot. |
|
Own the full E/H reference of a general guide, flat, as the seam will take it. |
|
What a declared transverse field becomes in a guide's channels. |
- solprop.translate.modal_tables.DEFAULT_GRAM_ATOL = 0.0001#
Largest sampled Gram defect
norm(W - I, 2)a table admits at a knot.A provider normalises its fields over the whole physical domain and deliberately does not renormalise a caller’s rule, so a rule that truncates the fields, or resolves them badly, returns a basis whose sampled Gram is not the identity – and every sample is still
valid, because the provider refused nothing. The table is where that has to be caught, before an interpolated basis carries the defect into a propagation.The value is the loosest defect a provider in this package can legitimately produce. The step-index rule certifies the cladding power outside it below 1e-12 of the total, so its defect is quadrature error alone: measured 1.33e-15, 4.68e-15 and 4.76e-15 over six mixed HE/EH/TE/TM channels at radial orders 24, 48 and 96. The radial provider admits up to
solprop.modes.graded_index.MAX_EXTERIOR_TAIL_FRACTION= 1e-4 of a channel’s power beyond its matching radius, and the sampled Gram is short by exactly the power the rule discards, so that guard is the binding one and this budget is the same number.It is a construction budget, not a physical statement: a caller who deliberately integrates part of a cross-section passes its own, and a taper whose interface leaves the frozen panel edge needs the held-out
flux_atolofvalidate_modal_table()instead, which measures the interpolated basis.
- class solprop.translate.modal_tables.DenseModalTable(omega, z, samples, nodes, degenerate_blocks=(), gram_atol=0.0001, basis=None)[source]#
Bases:
DisplayedRetain full reference samples at every frequency and longitudinal knot.
- Attributes:
- omeganumpy.ndarray
Strictly increasing positive frequencies in rad/s, shape (n_omega_table,).
- znumpy.ndarray
Strictly increasing positions in m, shape (n_z_table,); one is a snapshot, otherwise at least
MIN_Z_KNOTS.- samplestuple of tuple of ModalSample
Full E/H reference knots indexed by z, then frequency, in one tracked gauge.
- nodesModalNodeRule
Common physical nodes and weights for the entire table.
- degenerate_blockstuple of tuple of int
The blocks the knots were tracked with, retained so that a held-out comparison can align a fresh solve in the same declared subspaces. A table tracked with a rotating degenerate pair cannot be validated without them.
- gram_atolfloat
Positive budget on the sampled Gram defect of every knot, default
DEFAULT_GRAM_ATOL. Construction refuses a basis above it.- basissolprop.modes.guide.ChannelBasis or None
The declared unitary change of basis the knots carry on top of the tracking gauge, or
Nonefor the provider’s own coordinates. It is retained so that a held-out comparison can put a fresh provider solve in the same coordinates before aligning it.- gram_defectfloat
The largest
norm(W - I, 2)over the knots, dimensionless: how far the sampled basis is from orthonormal in true power. Reported here so that a problem built from this table can carry it into its debug summary.
- basis: ChannelBasis | None = None#
- evaluate(omega, z)[source]#
Interpolate full E/H and the generator inside the table domain.
- Parameters:
- omegafloat
Angular frequency in rad/s inside the knot interval.
- zfloat
Position in m inside the knot interval, or the snapshot position.
- Returns:
- ModalSample
Dense interpolated sample; its flux is recomputed from interpolated E/H and its
boundary_residualis the largest of the bracketing knots’. Held-out validation is required before using this approximation.
- generator_derivative(omega, z)[source]#
Differentiate the dense generator interpolant in angular frequency.
- Parameters:
- omegafloat
Angular frequency in rad/s inside the knot interval.
- zfloat
Position in m inside the knot interval.
- Returns:
- numpy.ndarray
Generator derivative in s/(rad m), shape (n_modes, n_modes).
- nodes: ModalNodeRule#
- omega: Float[ndarray, 'n_omega_table']#
- samples: tuple[tuple[ModalSample, ...], ...]#
- z: Float[ndarray, 'n_z_table']#
- solprop.translate.modal_tables.MAX_GENERATOR_COUPLING = 1e-09#
Largest off-diagonal generator a per-channel index table admits, relatively.
A channel’s propagation constant is the diagonal of the provider’s generator, so a generator that couples its channels has no such constant: its flow is a matrix exponential, which is the
coupled_flowleaf and is built by the general modal route, not by this table. Both providers here are diagonal by construction and a tracked alignment is a permutation and a phase, so the measured off-diagonal norm is exactly zero; the budget is there to refuse a provider that is not, not to admit a small coupling.What it is measured against.
norm(G - diag G, 'fro')is compared with the smallest splitting between the channels,min |beta_i - beta_j|overi != j, and not with the size ofbetaitself. Two channels mix over a length set by the ratio of their coupling to their splitting, so that ratio is the physical quantity; comparing with|beta| ~ 1e7 1/mwould have admitted an off-diagonal of 1e-3 1/m between two channels split by 1 1/m, which mixes them completely in a few metres. The splitting is floored at64 eps |beta|_max, the rounding of the propagation constants themselves, so that an exactly degenerate pair still has a defined scale.What this does not detect. A coupling below 1e-9 of the smallest splitting, which turns the pair by less than 1e-8 rad over a beat length and is what the budget is for. A coupling that appears between the knots: only the sampled generators are examined. Anything about the z direction: a table is a snapshot. And a physically coupled basis that a provider has already diagonalised – this refuses a generator that is not diagonal, not a guide whose channels exchange power by some other mechanism.
- solprop.translate.modal_tables.MIN_OVERLAP_DOMINANCE = 0.5#
Smallest aligned physical overlap a tracked channel may keep.
The aligned diagonal of the cross-Poynting matrix is a dimensionless overlap whose correct value is one, so a dominance criterion is the right test: below a half the assignment is no longer identifying the same physical channel, whatever permutation it chose. A bound of
1e-8would be eight orders looser than the quantity it guards and would accept an almost arbitrary mis-track.Measured on the 250 nm strand’s degenerate HE11 pair, the weakest aligned overlap against the reference sample is 1.0, 0.99999948, 0.99985 and 0.99872 at frequency ratios 1.0, 1.01, 1.2 and 2.0 – so even a doubling of the frequency, far beyond any table knot spacing, stays three orders above this floor.
- solprop.translate.modal_tables.MIN_Z_KNOTS = 4#
Fewest longitudinal knots a table interpolates over, beside a single snapshot.
SciPy’s not-a-knot cubic silently degrades to a parabola on three points and to a straight line on two, so a table built on two or three positions would report an interpolation order it does not have. One position is a snapshot and is interpolated in frequency alone.
- class solprop.translate.modal_tables.ModalTransformTables(omega, omega_fine, z_edges, synthesis, projection, electric, magnetic, backward_electric, backward_magnetic, reciprocal_gram, coordinate_dual, node_xy, weights, region_index, source_frequency_factor, valid, profile_error, factors, gauge_id, model_id, policy_id)[source]#
Bases:
DisplayedOwn the full E/H reference of a general guide, flat, as the seam will take it.
Built from a
DenseModalTablebymodal_transform_tables(), or along a moving guide bysolprop.translate.modal_path. It is flat – arrays, not a nest of samples – because that is what a backend can be handed, and it is thetransform.modal_tablesleaf of aKernelProblem.factorsis alwaysNone; the denseelectricandmagneticreferences, thecoordinate_dualand the execution mapssynthesisandprojectionare filled. Frequency is the last axis and is contiguous. Both maps are tabulated on the coarse ``omega``: the state is a coarse spectrum and the fine grid is zero everywhere butgrids.crop_index, so a synthesis tabulated onomega_finewould carry columns no run can read. The oversampled grid is where the field lives, not where the map does: the pass synthesises on the coarse grid and places the result in the fine spectrum atcrop_index.- Attributes:
- omeganumpy.ndarray
Coarse angular frequencies in rad/s, shape (n_omega,), increasing and non-negative. On a
KernelProblemit isgrids.omegaitself, whose first bin is zero on a field grid, with the inactive bins explicitly zero rather than evaluated.- omega_finenumpy.ndarray
Fine angular frequencies in rad/s, shape (n_omega_fine,), increasing and non-negative; the problem’s fine spectrum, by the same rule.
- z_edgesnumpy.ndarray
Stored positions in m, shape (n_z,), increasing; they are the edges the interval-indexed arrays are counted over, one interval per pair of positions and one for a single snapshot.
- synthesis, projectionnumpy.ndarray or None
The execution maps, supplied together or not at all, both on
omega.synthesisis S in V/(m sqrt(W)), shape(n_intervals, n_deg, n_nodes, 3, n_modes, n_omega);projectionis Q in V m/sqrt(W), shape(n_intervals, n_deg, n_modes, n_nodes, 3, n_omega), carrying the area weights and the reciprocal normalisation and excluding the separatesource_frequency_factor. The leading pair of axes is a Chebyshev expansion along z inside each interval, so one interval carrying a single coefficient (degree zero) is a constant map.- electric, magneticnumpy.ndarray
Reference E and H in V/m and A/m per sqrt(W), shape (n_z, n_nodes, 3, n_modes, n_omega).
- backward_electric, backward_magnetic, reciprocal_gramnumpy.ndarray or None
Reciprocal partners and their bilinear pairing, supplied together or not at all.
- coordinate_dualnumpy.ndarray or None
Amplitude recovery from a physical field, shape (n_z, n_modes, n_nodes, 6, n_omega): the first three components pair with E and the last three with H, in sqrt(W) m/V and sqrt(W) m/A.
- node_xynumpy.ndarray
Node positions in m, shape (n_z, n_nodes, 2).
- weightsnumpy.ndarray
Positive area weights in m², shape (n_z, n_nodes).
- region_indexnumpy.ndarray
Non-negative int32 material index per node, shape (n_nodes,).
- source_frequency_factornumpy.ndarray
The
-i omegathe projected polarisation is multiplied by, shape (n_omega,), in rad/s.- validnumpy.ndarray
Fine-grid support mask, shape (n_z, n_modes, n_omega_fine).
- profile_errornumpy.ndarray
Declared non-negative profile error per interval and coarse frequency, shape (n_intervals, n_omega), dimensionless.
- factorsobject or None
A factored representation; not implemented, so refused unless
None.- gauge_id, model_id, policy_idstr
Non-empty phase/coordinate, physical-model and numeric-policy identities.
- backward_electric: Complex[ndarray, 'n_z_backward_electric n_nodes_backward_electric 3 n_modes_backward_electric n_omega_backward_electric'] | None#
- backward_magnetic: Complex[ndarray, 'n_z_backward_magnetic n_nodes_backward_magnetic 3 n_modes_backward_magnetic n_omega_backward_magnetic'] | None#
- electric: Complex[ndarray, 'n_z_electric n_nodes_electric 3 n_modes_electric n_omega_electric']#
- property frozen_rule: bool[source]#
Return whether one node rule was broadcast across every position.
A record built over several positions from one rule – the same Cartesian nodes and the same area weights at every z – is the frozen-rule case. It is legitimate and it is not free: along a path the panel edge leaves the material interface, and the interpolated basis’s flux defect grows linearly with that displacement (4.9e-7, 9.8e-7 and 1.95e-6 at a quarter, a half and the whole of a 1e-5 relative radius change). A taper needs sections with their own rules (
solprop.translate.modal_path); a snapshot is trivially frozen.- Returns:
- bool
True when
node_xyandweightsdo not vary along z.
- property identity: Mapping[str, JsonValue][source]#
Return the scalar identity a run records and a resume compares.
The record is
eq=False: two of them are never compared by value, and a resume must not compare megabytes of E and H field by field either. What enters identity is this mapping – the three declared IDs, the six extents, the region count, the band and position endpoints, which optional representations are present, the frozen-rule flag and the profile-error budget. Every entry is a scalar, a string, a boolean or a list of scalars; no array is included by value.A factored map enters by its declared
kindtag, never by itsrepr: a defaultreprcarries a memory address, which is not stable across the two processes a resume compares.The profile-error budget is the caller’s declared one, which on the general modal route is the guide’s measured error; it therefore enters at
MEASURED_DIGITSsignificant digits, exactly assolprop.modes.guide.ModalSampling.identityrecords the same number, so that the two agree and a re-solve on another BLAS does not move either of them.- Returns:
- mapping
JSON-serialisable scalars, stable across processes.
- magnetic: Complex[ndarray, 'n_z_magnetic n_nodes_magnetic 3 n_modes_magnetic n_omega_magnetic']#
- node_xy: Float[ndarray, 'n_z_xy n_nodes_xy 2']#
- omega: Float[ndarray, 'n_omega_modal']#
- omega_fine: Float[ndarray, 'n_omega_fine_modal']#
- profile_error: Float[ndarray, 'n_intervals_profile_error n_omega_profile_error']#
- projection: Complex[ndarray, 'n_intervals_projection n_deg_projection n_modes_projection n_nodes_projection n_components_projection n_omega_projection'] | None#
- reciprocal_gram: Complex[ndarray, 'n_z_reciprocal n_modes_reciprocal_out n_modes_reciprocal_in n_omega_reciprocal'] | None#
- region_index: Int32[ndarray, 'n_nodes_region_index']#
- source_frequency_factor: Complex[ndarray, 'n_omega_source_factor']#
- synthesis: Complex[ndarray, 'n_intervals_synthesis n_deg_synthesis n_nodes_synthesis n_components_synthesis n_modes_synthesis n_omega_synthesis'] | None#
- valid: Bool[ndarray, 'n_z_valid n_modes_valid n_omega_fine_valid']#
- weights: Float[ndarray, 'n_z_weights n_nodes_weights']#
- z_edges: Float[ndarray, 'n_edges_modal']#
- class solprop.translate.modal_tables.ProjectedLaunch(amplitudes, captured, residual, residual_share)[source]#
Bases:
DisplayedWhat a declared transverse field becomes in a guide’s channels.
The result of
project_transverse_field(): the channel amplitudes, the power those amplitudes carry, and what the guide’s bound channels do not carry of the declared field. The last is named rather than discarded, because a projection that captured a tenth of what was declared and said nothing would be a silent change of the initial-value problem.- Attributes:
- amplitudesnumpy.ndarray
The channel amplitudes, complex128, shape
(n_modes, n_omega), in the square root of watts – the true-power amplitude every route uses (seedocs/dev/decisions.md), because the basis is flux-normalised.- capturednumpy.ndarray
The longitudinal Poynting flux the projected field carries, float64, shape
(n_omega,), in W. It is the physicalint Re(E x conj(H))/2 . z dAof the projected field, formed from the guide’s own stored E and H, which equalsa.H W aand is notsum_m |a_m|^2unless the basis is exactly orthonormal.- residualnumpy.ndarray
Share of the declared field the bound channels do not reproduce, float64, shape
(n_omega,), dimensionless and non-negative – and, likeresidual_sharebelow, not clipped at one:sum_q w_q |E - sum_m a_m e_m|^2 / sum_q w_q |E|^2at each bin, in the guide’s own quadrature norm. It isnanwhere the declared field is identically zero, because there is no share of nothing.- residual_sharefloat
The same quantity over the whole declared field, weighting each bin by its own declared norm rather than equally: one number for the projection. It is not clipped at one: on a basis that is not orthonormal the flux projection can overshoot, leaving a residual larger than the declared field itself, and a share above one says exactly that. Clipping it would report a projection that missed everything as one that missed all of it, which are different statements.
- Raises:
- ModeError
If the arrays disagree in shape, or the residual share is not finite and non-negative.
- amplitudes: Complex[ndarray, 'n_modes_launch n_omega_launch']#
- captured: Float[ndarray, 'n_omega_captured']#
- residual: Float[ndarray, 'n_omega_residual']#
- solprop.translate.modal_tables.align_modal_sample(reference, sample, nodes, *, degenerate_blocks=())[source]#
Align channels by physical overlap, phase and declared subspaces.
- Parameters:
- referenceModalSample
Previous sample on the same physical Cartesian nodes.
- sampleModalSample
New full-vector sample with the same retained channel count.
- nodesModalNodeRule
Common physical nodes and area weights in m².
- degenerate_blockssequence of sequence of int, optional
Disjoint channel-index blocks permitted to rotate. The complete generator is rotated too, so a near-degenerate block remains physically equivalent.
- Returns:
- ModalSample
Aligned E/H, generator, flux and reciprocal data in reference coordinates.
- Raises:
- ModeError
If the common domain, validity, channel count or overlap is unresolved.
- solprop.translate.modal_tables.apply_channel_basis(sample, basis)[source]#
Put one tracked sample in a declared channel basis, splitting the two cases.
The transformed generator is
U.H diag(beta) U, which stays diagonal when the block’s propagation constants are equal and becomes a coupled matrix flow when they are not. Both are admitted, and they are not the same object:an exactly degenerate block leaves the generator diagonal, and the generator is carried over rather than multiplied out (below);
a split block leaves it coupled, and the product is evaluated. That block is
linear.coupled_flow, executed bynumpy_refandnumba_cpuand built by the general modal route from the transformed generator (solprop.translate.general.coupled_block()). Any refusal is the route’s, where a flow can be built, and this function’s job is only to say which of the two the declaration produced.
The equality is judged at the rounding of the propagation constants, the same floor
_channel_splittinggives a degenerate pair:64 eps max|beta|. The two HE11 parities of a step-index strand satisfy it exactly – measured 0.0 relative – and a pair that is degenerate only to a part in 1e-9 is a physically split pair whose partners beat, which is the case this refuses. The generator must also already be diagonal to that same rounding, which is what the two conditions together makeG' = Gan identity rather than a product:Uis the identity outside the blocks and each block isg_btimes the identity inside it, so it commutes withU.Where every block is degenerate the generator is carried over exactly, not multiplied out. Evaluating
U.H G Unumerically injects rounding into quantities that are exactly zero: a lossless provider hasRe G = 0to the bit, and the product put a spuriousRe G = +1.1e-261/m there – gain, which_channel_index_tablerefuses outright, and anIm n_effof the wrong sign at the 1e-33 level. Taking the exact value is the identity the two gates above have just certified – to their own floor, which is64 eps max|beta|, 1.55e-7 1/m on a step-index strand and 1.4e-14 of the propagation constants themselves. Below that floor the carried-over generator and the evaluated product differ, and this keeps the exact one, so a degenerate declaration is bitwise independent of the split path.Where a block is split the product is evaluated, because there is then no identity to carry over: the off-diagonal is the physics, not the rounding. The diagonal that comes out of it is checked for gain exactly as the carried-over one is, so a conjugation error is still caught.
The invariance gate is certified to the declaration’s own budget. The transform must not change
norm(W - I, 2), which a unitary one cannot; the budget is the rounding of the product orBASIS_UNITARY_ATOL, whichever is larger, because a transform that is unitary only to that budget moves the measured norm by its own unitary defect and the declaration has already admitted it at exactly that size.- Parameters:
- samplesolprop.modes.vector.ModalSample
A tracked sample in the provider’s own channel coordinates.
- basissolprop.modes.guide.ChannelBasis
The declared change of basis.
- Returns:
- solprop.modes.vector.ModalSample
The sample in the declared coordinates, with the composed gauge identity.
- Raises:
- ModeError
If the transform’s width disagrees with the sample, the generator is not already diagonal, the evaluated product of a split block shows gain, or the transform changes the basis’s measured departure from orthonormal, which a unitary one cannot.
- solprop.translate.modal_tables.channel_area(sample, nodes, channel)[source]#
Return the scalar effective area of one channel of a vector sample.
The scalar definition
A_eff = (int |E|^2 dA)^2 / int |E|^4 dAwith|E|^2 = E . E*summed over the three Cartesian components, integrated on the caller’s rule. It is a surrogate: it is what the legacy mode-averaged interfaces mean by an area, and it is not the nonlinear coefficient of a vector channel, which is the full contraction ofsolprop.nonlinear.normalisation.modal_kerr_coefficient()over the material regions that respond.- Parameters:
- sampleModalSample
Internal-convention sample whose fields are normalised to unit power.
- nodesModalNodeRule
The rule the sample was evaluated on; supplies the area weights in m².
- channelint
Channel index on the mode axis.
- Returns:
- float
Effective area in m².
- solprop.translate.modal_tables.composed_gauge(gauge_id, basis)[source]#
Return the gauge a declared basis leaves on top of a tracking gauge.
The order is fixed and it is documented here. A knot is first put in the tracking gauge – the permutation, the phase and the degenerate-block SVD alignment
align_modal_sample()applies – and the declared basis is applied after it, so the composed transform isT_track Uand the composed identity is"<tracking gauge>+<basis gauge>". That order is what a resume has to reproduce, and it is the one that leaves a run with no declared basis bitwise what it was: the tracking is untouched and the declaration is a second, independent step on top of it.- Parameters:
- gauge_idstr
The tracking gauge’s own identity, the provider’s.
- basissolprop.modes.guide.ChannelBasis or None
The declared change of basis, or
None.
- Returns:
- str
The composed identity, unchanged when nothing is declared.
Examples
>>> from solprop.translate.modal_tables import composed_gauge >>> composed_gauge("step-index-exact", None) 'step-index-exact'
- solprop.translate.modal_tables.generator_is_coupled(table)[source]#
Return whether a tracked table’s generator couples its channels anywhere.
The one predicate both callers of the split-block route ask: the guide constructor, to know whether its per-channel index tables are surrogates rather than propagation constants, and the route, to know whether to build
linear.coupled_flow(solprop.translate.general.coupled_block()). It is judged at the rounding of the propagation constants themselves,64 eps max|beta|, the same floorapply_channel_basis()gates its declared blocks at, so that a generator diagonal to the bit is diagonal here too.- Parameters:
- tableDenseModalTable
The tracked reference, in whatever basis was declared on it.
- Returns:
- bool
True when some knot’s generator has an off-diagonal above that floor.
Examples
>>> import numpy as np >>> from solprop.modes.graded_index import ( ... GradedIndexProvider, PowerLawPermittivity, ... ) >>> from solprop.modes.material import IndexMaterial >>> from solprop.translate.modal_tables import ( ... generator_is_coupled, sample_modal_table, ... ) >>> guide = GradedIndexProvider( ... permittivity=PowerLawPermittivity(IndexMaterial(1.45), 0.005), ... exterior=IndexMaterial(1.45 * np.sqrt(1 - 2 * 0.005)), ... radius=25e-6, family="LP", count=2, elements=16, extent=2.0, ... ) >>> omega = np.linspace(1.80e15, 1.86e15, 4) >>> table = sample_modal_table( ... guide, omega, np.array([0.0]), guide.node_rule(1.80e15, n_r=2) ... ) >>> generator_is_coupled(table) False
- solprop.translate.modal_tables.modal_transform_tables(table, omega, *, profile_error, omega_fine=None, z=None, policy_id='dense-knots-cubic-frequency')[source]#
Build the flat seam record from a dense reference table.
The one constructor of
ModalTransformTables. It evaluates the table on the grid a run will use – which need not be the table’s own knots, and in general is not – and stores the fields, the reciprocal partners, the coefficient dual, the two execution maps and the node geometry as arrays.Outside the table’s band the record is explicitly zero, and the fine grid’s
validmask is false there. A canonical field grid starts at zero and runs past any guide’s support, so a leaf tabulated on it necessarily leaves the band; what must not happen is an evaluated profile there, which extrapolates a mode past the frequencies it was solved at. A zero reference is a zero source and a zero synthesised field: a run whose active band leaves the guide’s support is refused where the problem is assembled, not silently propagated against zeros.The execution maps are built for a snapshot table only. S and Q are stored as one Chebyshev coefficient per interval, which is exact when the map does not vary along z and an average when it does, so a table sampled over a path of positions gets
synthesisandprojectionofNoneand the general route refuses it: a moving basis needs sections with their own node rules, whichsolprop.translate.modal_pathbuilds. A snapshot is broadcast across every requested position, which is the frozen-rule caseModalTransformTables.frozen_rulenames.- Parameters:
- tableDenseModalTable
Tracked reference knots. Requested frequencies outside its band are stored as zeros; requested positions outside a path table’s own are refused, and a snapshot is broadcast to all of them.
- omeganumpy.ndarray
Coarse angular frequencies in rad/s, shape (n_omega,), increasing.
- profile_errorarray_like or float
Declared non-negative profile error of the representation, per interval and coarse frequency or one value for all of them. It is a budget the caller has measured, for example with
validate_modal_table(); nothing here infers it.- omega_finearray_like or None, optional
Fine angular frequencies in rad/s;
Noneuses the coarse grid, which is what a run with no oversampling declares.- zarray_like or None, optional
Positions in m to store;
Noneuses the table’s own.- policy_idstr, optional
Identity of the numeric policy this record was built under.
- Returns:
- ModalTransformTables
The validated flat record, with
factorsleftNoneand thecoordinate_dualfilled.
- Raises:
- ModeError
If a position leaves a path table’s domain, or the record does not validate.
- solprop.translate.modal_tables.project_transverse_field(tables, electric)[source]#
Project a declared transverse electric field onto a guide’s channels.
The launch a caller declares as a field rather than as a channel: the amplitude of each channel in a declared
E(x, y, omega)sampled at the guide’s own quadrature nodes, with the power captured and the remainder named.The map is the coordinate dual, not the source projection. The amplitude of a field in a flux-normalised basis is
a = W^-1 BwithB_m = int(E x h_m* + e_m* x H)_z dA / 4, the same Hermitian Poynting pairing the flux metric is built from, and that is whattables.coordinate_dualcarries.Q, the record’s other map, contracts a polarisation with the conjugated electric profile and carries the reciprocal normalisation for a source term; it is a different operation and gives a different number.Only E is declared, so the H half is closed by the forward-field assumption. For any superposition of these channels travelling forward, the two halves of
B_mare equal – the Poynting cross matrix of a lossless bound basis is Hermitian – soB_m = 2 int(E x h_m*)_z dA / 4and the declared E alone determines the amplitudes. Measured on the step-index parity pair, a field synthesised from known amplitudes comes back through this map to 1.412e-15 of them, which is rounding and not the basis’s Gram defect (1.3e-6), so the closure is exact for this package’s providers rather than approximate. (Its last digits move with the BLAS thread count, as a 288-node reduction’s rounding does; the quoted number is one thread everywhere, and the bound the test asserts is 1e-13.) That exactness is a statement about a lossless basis: the Hermitian symmetry of the Poynting cross matrix is what makes the two halves equal, and a complexPermittivityLawbreaks it, so on such a basis the closure would be wrong by an amount only a growing residual share would show. No such basis can reach here: both providers this package ships refuse a complex permittivity when they solve, by name ("provider material differs from its lossless step-index law"and"radial provider requires positive lossless permittivity"), so the condition is enforced upstream and a future lossy provider must re-derive the closure rather than inherit it. A field that is not forward-propagating – a declared backward or evanescent component – is not what this map returns either, and nothing here can see the difference; declare the amplitudes directly if that is wanted.What is not captured is named. The bound channels span what they span: a declared field with power in radiation modes, in a channel the guide did not solve for, or simply outside the node rule’s reach leaves a residual, and ProjectedLaunch.residual reports it as a share of the declared field in the guide’s own quadrature norm. Reprojecting a field creates a new initial-value problem; it is not a resume path.
- Parameters:
- tablesModalTransformTables
The guide’s reference, which must carry its coordinate dual.
- electricnumpy.ndarray
The declared transverse field at the guide’s nodes, real or complex (a real profile is the common case and is taken as it is), shape
(n_nodes, n_components, n_omega), in V/m, on the record’s own coarse frequency axis. A profile that does not depend on frequency is broadcast to it by the caller.
- Returns:
- ProjectedLaunch
The amplitudes, the captured flux and the residual.
- Raises:
- ModeError
If the record carries no coordinate dual, or the declared field does not lie on its nodes, components and frequencies.
- solprop.translate.modal_tables.sample_modal_table(provider, omega, z, nodes, *, degenerate_blocks=(), basis=None, gram_atol=0.0001)[source]#
Build dense knots with frequency and longitudinal physical-overlap tracking.
- Parameters:
- providerModalProvider
Internal-convention bound-mode physics provider.
- omeganumpy.ndarray
At least four increasing positive frequencies in rad/s, shape (n_omega,).
- znumpy.ndarray
Increasing longitudinal positions in m, shape (n_z,); one position, or at least
MIN_Z_KNOTSof them.- nodesModalNodeRule
Common physical nodes; changing material ownership requires separate tables.
- degenerate_blockssequence of sequence of int, optional
Declared blocks passed to the physical-overlap alignment, and retained on the table so that held-out validation aligns a fresh solve the same way.
- basissolprop.modes.guide.ChannelBasis or None, optional
A declared unitary change of channel coordinates, applied to every knot after the tracking gauge and gated on the exact degeneracy of each of its blocks (
apply_channel_basis(),composed_gauge()). Every block it acts inside must be one of degenerate_blocks: tracking rotates freely inside a declared block, so a transform of a block the tracking did not know about would be applied on top of an ordering the eigenvalues cannot resolve.Nonekeeps the provider’s own coordinates, and then this function is bitwise what it was.- gram_atolfloat, optional
Budget on each knot’s sampled Gram defect, default
DEFAULT_GRAM_ATOL; the table refuses a basis above it.
- Returns:
- DenseModalTable
Full E/H knots, with frequency tracking after each z-row entrance alignment.
- Raises:
- ModeError
If the provider has an unsupported convention or crosses a physical kink, if basis acts on a block the tracking was not given or on one whose propagation constants differ, or if the sampled basis is further from orthonormal than gram_atol.
- solprop.translate.modal_tables.sample_provider_table(provider, channel, omega, *, z=0.0, nodes=None, include_area=True)[source]#
Sample one channel of a modal provider into a dense index snapshot.
The sibling of
solprop.modes.tabulated.sample_index_table()for a provider rather than a mode.betais the provider’s own generator at the knots, read off the tracked diagonal, so the channel is followed by physical overlap and not by eigenvalue order;beta_1andbeta_2are the interpolant’s, the dense table’s cubic in frequency differentiated, because a provider declares no analytic frequency derivative.Nothing here certifies those derivatives, and
validate_modal_table()does not either: it measures a profile, a generator and a flux defect, and no frequency derivative at all. Measured held-out against fresh-solve five-point differences on the reference parabolic sector oftests/modes/test_modal_tables.py, over a +-2 % band,beta_1is 4.4e-13 relative at nine knots whilebeta_2is 2.8e-2 – eleven orders apart, because a table’s cubic reproduces a nearly constant group delay exactly and still has almost no curvature. Treatbeta_1as the interpolant’s, andbeta_2as a diagnostic (measured by a development study of the provider-table derivatives;solprop.modes.guide.ProviderChannelMode).- Parameters:
- providerModalProvider
Internal-convention bound-mode provider that names its channels.
- channelint
Channel index on the provider’s mode axis.
- omeganumpy.ndarray
At least four increasing positive frequencies in rad/s, shape (n_omega,).
- zfloat, optional
Snapshot position in m inside the provider’s z domain.
- nodesModalNodeRule or None, optional
Common physical rule;
Noneasks the provider for one at the lowest frequency of the band, where the power a rule discards is largest, so that its certified tail is a bound over the band.- include_areabool, optional
Sample the surrogate
channel_area()at every frequency.
- Returns:
- IndexTable
Sorted, immutable index samples with the interpolant’s slopes, carrying
derivative_valuesand asource_identitythat names the provider, the channel, the snapshot position, the area model and the derivative model.
- Raises:
- ModeError
If the frequencies, the channel, the rule or the sampled basis is invalid.
- solprop.translate.modal_tables.solved_in_table_basis(table, provider, omega, z)[source]#
Solve a fresh sample and put it in the table’s declared channel basis.
A held-out comparison asks the provider again and aligns the answer against the table’s own knots. The provider always answers in its coordinates, so a table carrying a declared basis must apply the same declaration before the alignment; otherwise the degenerate-block SVD would be asked to discover the declared transform, which it can only do up to the same block rotation the declaration exists to fix.
- Parameters:
- tableDenseModalTable
The table whose basis the solve is put in.
- providersolprop.modes.vector.ModalProvider
The physics, asked again at one point.
- omegafloat
Positive angular frequency in rad/s.
- zfloat
Position in m.
- Returns:
- solprop.modes.vector.ModalSample
The fresh solve, in the table’s coordinates but not yet aligned to it.
- solprop.translate.modal_tables.transformed_sample(sample, transform, *, gauge_id)[source]#
Put a sample in new channel coordinates:
e' = e U, with every partner.The one place a channel transform is applied to a
ModalSample, shared by the tracking ofalign_modal_sample()– whose transform is a permutation, a phase and a degenerate-block rotation – and by the declared change of basis ofsolprop.modes.guide.ChannelBasis. The rules follow frome' = e Ualone: the Hermitian objects conjugate asU.H X Uand the bilinear reciprocal partners, which pair without a conjugate, carryU.conj().The boundary residual of a mixed channel is reported as the block’s largest. A combination
sum_n c_n e_nof unit-norm coefficients has a residual bounded bysum_n |c_n|times the largest, which for a unitary column is at mostsqrt(n_modes)times it, so this is a diagnostic and not a certificate; it is the rule the tracking has always used and one number for the set is what the validation ofModalSamplecompares.- Parameters:
- samplesolprop.modes.vector.ModalSample
The sample to re-express.
- transformnumpy.ndarray
The matrix
U, shape (n_modes, n_modes), whose columns are the new basis vectors written in the old one.- gauge_idstr
Non-empty phase and coordinate identity of the result.
- Returns:
- solprop.modes.vector.ModalSample
The same physics in the new channel coordinates.
- solprop.translate.modal_tables.validate_modal_table(table, provider, points, *, profile_rtol, generator_atol, flux_atol)[source]#
Compare dense interpolation with held-out physical provider solves.
The third defect is the flux defect
norm(W - I, 2)of the interpolated basis: an interpolated field is no longer an exact mode, and the spectral norm of its sampled Gram against the identity says by how much the basis has stopped being orthonormal in true power. It falls with the frequency knots like the profile error – measured 2.25e-8, 1.13e-9 and 5.96e-13 for 5, 9 and 17 knots on the 250 nm strand – but a held-out position also carries a provider effect that refinement in frequency does not remove: the node rule is frozen at the entrance radius, so on a taper the panel edge no longer sits on the material interface and the defect grows linearly with the displacement (4.9e-7, 9.8e-7 and 1.95e-6 at z = 0.25, 0.5 and 1.0 m for a 1e-5 relative radius change, 7.22e-7 at the report’s held-out z). Budget it accordingly, and split the table where an interface crosses a node.- Parameters:
- tableDenseModalTable
Reference table on fixed physical nodes. Its declared degenerate blocks are reused here, so a table tracked through a rotating degenerate pair is compared in the same subspaces it was built in.
- providerModalProvider
Independent fresh mode solves, in the same physical model.
- pointsnumpy.ndarray
Held-out (omega, z) pairs in rad/s and m, shape (n_points, 2).
- profile_rtolfloat
Positive relative E/H weighted norm budget, fixed before evaluation.
- generator_atolfloat
Positive absolute generator error budget in 1/m, fixed before evaluation.
- flux_atolfloat
Positive dimensionless budget on
norm(W - I, 2), fixed before evaluation.
- Returns:
- numpy.ndarray
Profile, generator and flux defects, shape (n_points, 3).
- Raises:
- ModeError
If a budget is invalid or a held-out error exceeds it.