Channel labels and modal tables#

Channel labels identify physical modes as frequency or geometry changes. A modal table samples their fields and propagation constants so a run can reconstruct a frequency-dependent basis. Resolution, overlap tracking, cutoff and omitted exterior power all affect whether that basis remains accurate.

Channel labels and dense reference tables#

Every mode carries a ChannelLabel: its family, azimuthal and radial order, parity, handedness and a stable display label. It is a member of the Mode protocol, so the label belongs to the mode rather than to its place in a set, and ModeCollection.channel_labels is simply its members’ labels in the order of the mode axis. A GradedIndexProvider produces one label per requested radial channel, and both providers publish them in identity["channels"], which is what ModalGuide checks its mode collection against. A label a family does not have is None, never a string standing for one: an axially symmetric TE0m or TM0m channel has no parity partner, so its parity is None. handedness is None on every real basis, and is filled where a guide declares a complex one: the circular partners of solprop.modes.guide.circular_pair() carry left and right in place of even and odd, by the convention General modal propagation derives and Polarisation and azimuthal modes fixes the sign of.

Parity means one thing across every family, and ChannelLabel’s docstring is where it is defined: "even" is the member whose transverse pattern is the \(\cos((n-1)\theta)\) one about the x axis — for \(n=1\) that is the x-polarised member — and "odd" is its quarter-turn partner. Each family reaches it its own way. An exact step-index \(HE_{nm}\) builds the pair from \(\cos(n\theta)\) and \(\sin(n\theta)\) on the radial component, and its “even” member is x-polarised on the axis. A Marcatili \(HE_{nm}\) is a rotation \(\phi\) of one pattern, and its “even” member is the unrotated \(\phi = 0\) — so MarcatiliMode(125e-6, "He", 1.0), which is x-polarised, is HE1,1:even, and its orthogonal_polarisation, the quarter turn \(\phi = \pi/(2n)\), is HE1,1:odd. Any other rotation is a gauge and not a parity: its parity is None and the angle goes in the display label. Note that the label’s azimuthal_order is the family index \(n\) — unlike the mode property of the same name, which is the field’s highest harmonic — while the parity names the \((n-1)\)-fold pattern the transverse vector field has.

Repeating a step-index parity or adding a second TE/TM parity is refused. A common physical step-index guide may have different mode-specific cladding tail radii; its collection uses a common enclosing domain.

sample_index_table(mode, omega, z=..., include_area=True) is the dense dispersion bridge beside IndexTable. It retains the source’s frequency derivative in a Hermite interpolant and records the snapshot position and source identity. Step-index areas are sampled at each frequency. Other modes retain their declared frozen-profile area. The resulting table does not supply electric or magnetic fields, and never extrapolates beyond its knots. It is an IndexTable subclass, so it also carries derivative_values and source_identity, and it is built with loss_interpolation="linear" rather than the table default "log": a lossless source has an imaginary index of exactly zero, which has no logarithm and which the logarithmic policy refuses outright.

solprop.translate.modal_tables.sample_modal_table retains full E/H samples on common physical nodes. It assigns channels by physical overlap, aligns their phases and optionally rotates declared degenerate subspaces. It rotates the complete generator and metric with the fields, including a near-degenerate block. The blocks a table was tracked with are retained on it, so a held-out comparison aligns a fresh solve in the same declared subspaces. A table interpolates over one z snapshot or at least four z knots, because SciPy’s not-a-knot cubic degrades silently to a parabola on three positions and to a straight line on two. An interpolated sample carries the largest boundary_residual of its bracketing knots. No compression is performed.

Construction gates the sampled Gram. A provider normalises over its whole physical domain and deliberately does not renormalise a caller’s rule, so a rule that truncates or under-resolves the fields returns a basis whose sampled \(W\) is not the identity while every sample is still valid. A table refuses a knot whose \(\lVert W-I\rVert_2\) exceeds gram_atol, whose default DEFAULT_GRAM_ATOL is 1e-4 – the loosest defect a provider here can legitimately produce, since the radial provider admits up to MAX_EXTERIOR_TAIL_FRACTION of a channel’s power beyond its matching radius and the sampled Gram is short by exactly the power the rule discards. The largest defect over the knots stays on the record as gram_defect. On the 250 nm strand it is 0.31, 2.41e-4, 1.24e-5, 2.92e-8 and 2.03e-14 at radial orders 3, 12, 16, 24 and 48; the six-channel 1.5 um guide is at rounding (1.33e-15) from order 24, and a parabolic GRIN sector is at 4.2e-14 on its own element rule.

The cutoff policy of a table is the providers’ own, applied when the table is built: a band in which a channel is below cutoff, or too near it for the frozen profile to be resolved, is refused by name at construction, and a table never extrapolates past its knots into a band it does not carry. Nothing is masked and no channel is silently dropped.

Tracking follows the physical channel through a crossing: the assignment is by the overlap of the fields, so two channels whose propagation constants exchange order across the band keep their identities, where sorting by eigenvalue would swap them.

modal_transform_tables(table, omega, profile_error=...) builds ModalTransformTables, the flat record a run’s kernel problem carries as its transform.modal_tables leaf. It evaluates the table on the frequency 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 (the synthesis and projection of general modal propagation) and the node geometry as arrays, with frequency last and contiguous. Outside the table’s band the record is explicitly zero rather than extrapolated, and a run whose active band leaves the guide’s support is refused when the problem is assembled. The execution maps are built for a snapshot table only; a moving guide’s maps come from the path sampling described above.

coordinate_dual recovers amplitudes from a physical field: contracted against the six components (E, H) at every node it returns the amplitude vector that synthesised them, because it is W^-1 times the same Hermitian Poynting pairing the flux metric is built from. Its first three columns are in sqrt(W) m/V and its last three in sqrt(W) m/A, and its two longitudinal columns are zero, since only the transverse components carry longitudinal flux. It is for amplitude recovery, not for nonlinear projection, which carries the separate -i omega of source_frequency_factor.

profile_error is declared by the caller, per interval and coarse frequency: nothing in the constructor infers the accuracy of its own representation. A snapshot declares one interval; a path declares one per pair of stored positions.

validate_modal_table compares held-out solves against budgets supplied before the comparison; frequency derivatives can be checked through generator_derivative. Physical kinks require separate sections. Besides the weighted field and generator defects it reports the flux defect \(\lVert W-I\rVert_2\) of the interpolated basis, which is what says how far the interpolant has stopped being an orthonormal set in true power. That column falls with the frequency knots like the field defect (2.25e-8, 1.13e-9 and 5.96e-13 for 5, 9 and 17 knots on the 250 nm strand), but at a held-out position it is a property of the provider rather than of the interpolation: the node rule is frozen at the entrance radius, so along a taper the panel edge no longer sits on the material interface and the defect grows linearly with the displacement (4.84e-7, 9.68e-7 and 1.94e-6 at z = 0.25, 0.5 and 1.0 m for a 1e-5 relative radius change). Budget that floor, or rebuild the rule along the path, as ModalGuide.from_provider does for a moving guide.

A guide built by ModalGuide.from_provider is something to run, not only to inspect: Simulation.from_waveguide(..., provider=guide) and prop_waveguide(..., provider=guide) propagate its three-component fields on numpy_ref, numba_cpu and native (on the CPU and on the qualified GPU builds), and the general modal page is the whole description. The guide carries its own ModalSampling – the knots, the position, the node rule, the declared degenerate blocks and the measured profile interpolation error – and a run rebuilds its dense reference from that; a guide built by from_step_index carries none, because nothing there declares a transverse resolution, and a run refuses it by name. A region’s nonlinear response is declared on the region, with MaterialKerr, by chi3 or by n2. A GradedIndexProvider builds its own two regions and so takes its core’s responses as core_responses=(...); its exterior is always linear, because a bound radial sector’s exterior is the exact Bessel continuation and holds at most MAX_EXTERIOR_TAIL_FRACTION of the power, at nodes the rule barely resolves.