solprop.modes.guide#
Build Poynting-normalised bound-mode providers with explicit material ownership.
Module Attributes
Largest |
|
Largest relative disagreement a step-index provider admits between a region's declared permittivity law and the index its channels were solved with. |
|
Largest relative disagreement a guide admits between its tables and its provider. |
Functions
|
Declare the circular partners of one exactly degenerate parity pair. |
|
Return a provider's declared channels as labels, in its own order. |
|
Build interface-split Cartesian nodes from the existing polar panel rule. |
Classes
|
Declare the unitary coordinates a guide's degenerate channels are given in. |
|
What a provider channel is solved again from: its provider, index and rule. |
|
Associate a vector provider with the existing ordered mode collection. |
|
Record how a moving guide was sampled along z, and what that measured. |
|
Record what a guide's channels were solved on, and what that measured. |
|
One labelled channel of a modal provider, carrying its sampled dispersion. |
|
Evaluate exact bound step-index E/H using independent full-domain power norms. |
- solprop.modes.guide.BASIS_UNITARY_ATOL = 1e-12#
Largest
norm(U.H U - I, 2)a declared change of basis is still called unitary.Dimensionless. A declared transform is admitted only where it is a unitary change of coordinates of an exactly degenerate block: the flux metric then transforms as
W' = U.H W U, which is the identity whereverWwas, and the generatorU.H diag(beta) Ustays diagonal. A nonunitary transform does neither – it is a leaky or skewed basis whose metric is genuinely off the identity and whose flow is a matrix exponential – and nothing in this package has measured one, so it is refused here rather than approximated.The number.
circular_pair()’s matrix has entries+-1/sqrt(2)and+-i/sqrt(2);1/sqrt(2)is exact to a half ulp and the products that formU.H Ucancel to the last bit, so its measured defect is at the 1e-16 level. A budget of 1e-12 is four orders above that – room for a matrix assembled from measured numbers rather than from a literal – and twelve orders below any departure a reader would call a different basis.The room is real, because every later gate honours this budget. A transform that is unitary only to
Emoves a knot’s measurednorm(W - I, 2)bynorm(E + E.H, 2), which is this defect and not the metric’s, so the per-knot invariance check ofsolprop.translate.modal_tables.apply_channel_basis()takes this constant as its floor rather than the rounding of its own product (2.8e-14 for two channels, which would refuse a declaration that had just passed here). The four orders are therefore the room a declaration actually has, not the room it has until the next gate.
- class solprop.modes.guide.ChannelBasis(transform, blocks, labels, gauge_id)[source]#
Bases:
DisplayedDeclare the unitary coordinates a guide’s degenerate channels are given in.
A guide’s channels arrive in whatever basis its provider solved them in, which for an azimuthally degenerate family is the real parity pair: the
cosandsinpatternsChannelLabeldefines. Any unitary combination of an exactly degenerate block is as good a basis, and one of them – the circular pair ofcircular_pair()– diagonalises the isotropic Kerr tensor’s polarisation dependence. This record is the declaration of that choice: the matrix, the blocks it acts inside, the labels the transformed channels carry and the identity a file records and a resume compares.The transform acts on the reference, not on the state. With
ethe provider’s nodal field rows andUthis matrix, the declared basis ise' = e U, so a physical field whose reference-basis amplitudes arechas declared-basis amplitudesd = U.H c: the columns ofUare the new basis vectors written in the old one. Every derived object follows from that one rule –W' = U.H W U,G' = U.H G U,Q' = U.H Q,S' = S U– and the noise draw made in the reference basis reaches the declared one asU.Htimes itself.Whether a block is degenerate is decided where the table is built, not here (
solprop.translate.modal_tables.apply_channel_basis()):U.H diag(beta) Uis diagonal when the block’s propagation constants are equal and is a coupled matrix flow,linear.coupled_flow, when they are not.- Attributes:
- transformnumpy.ndarray
The unitary
U, shape (n_modes, n_modes), dimensionless. It is the identity outside blocks, and unitary toBASIS_UNITARY_ATOL.- blockstuple of tuple of int
Disjoint channel-index blocks
Uacts inside, each with at least two members. They must be blocks the guide declared degenerate.- labelstuple of ChannelLabel
One label per channel of the declared basis, in row order: the provider’s own outside the blocks and the transformed ones inside them.
- gauge_idstr
Non-empty identity of this declaration, composed into the tables’ and the metric’s
gauge_idso that a run in one basis cannot resume in another.
- Raises:
- solprop.exceptions.ModeError
If the matrix is not square, finite, unitary or block-diagonal on blocks, if the blocks are not disjoint valid sets of at least two channels, if the label count disagrees with the matrix, or if the identity is empty.
Examples
>>> from solprop.modes.guide import circular_pair >>> from solprop.modes.protocol import ChannelLabel >>> pair = ( ... ChannelLabel("HE", 1, 1, "even", None, "HE1,1:even"), ... ChannelLabel("HE", 1, 1, "odd", None, "HE1,1:odd"), ... ) >>> basis = circular_pair(pair, (0, 1)) >>> basis.gauge_id 'circular(0,1)' >>> [label.display_label for label in basis.labels] ['HE1,1:left', 'HE1,1:right']
- property identity: Mapping[str, JsonValue][source]#
Return what a run records of this declaration, and a resume compares.
The matrix enters by value: two different unitary transforms of the same block are two different bases, and the block list alone does not tell them apart. Its entries are declared numbers, not measurements, so they enter exactly, as a declared
chi3or a knot does (solprop.modes.guide.ModalSampling.identity()).- Returns:
- collections.abc.Mapping
JSON-serialisable scalars and lists of scalars, stable across processes.
- labels: tuple[ChannelLabel, ...]#
- transform: Complex[ndarray, 'n_modes_basis_out n_modes_basis_in']#
- class solprop.modes.guide.ChannelSource(provider, index, nodes, z, basis=None)[source]#
Bases:
objectWhat a provider channel is solved again from: its provider, index and rule.
A
ProviderChannelModeis a sampled snapshot, and some questions need the fields again at a frequency or a position the snapshot did not keep – a scalar model’s effective area at its pump, above all.ModalGuide.from_provider()gives every channel one, so that the channel can ask the provider itself rather than its caller reaching into the translation layer.- Attributes:
- providerModalProvider
The guide’s full-vector provider.
- indexint
The channel’s index on the provider’s mode axis, after basis, from zero.
- nodesModalNodeRule
The rule the guide was sampled on: the entrance rule of a moving guide, which the provider is asked for again at every position (
solprop.translate.modal_path.path_rule()).- zfloat
The position in m the snapshot, or a moving guide’s entrance, was solved at.
- basisChannelBasis or None
The guide’s declared change of channel basis, applied to every fresh solve before the channel is read, or
Nonefor the provider’s own.
- basis: ChannelBasis | None = None#
- nodes: ModalNodeRule#
- provider: ModalProvider#
- solprop.modes.guide.MATERIAL_AGREEMENT_RTOL = 1e-06#
Largest relative disagreement a step-index provider admits between a region’s declared permittivity law and the index its channels were solved with.
The modes own the eigenproblem’s indices (
StepIndexMode.coreandcladding) and the regions own the material laws; the two describe the same material and nothing else made them agree. The regions’ laws are what a declaredn2is converted against (solprop.modes.material.MaterialKerr.susceptibility()) and what a guide’s identity records, so a core region declared at 2.0 beside channels solved at 1.45 would propagate the 1.45 fields with achi390 % too large and nothing downstream would say so.The budget is on the permittivity, and 1e-6 is chosen to admit a law evaluated by two paths – a glass law asked for its index and the same law asked for its permittivity – while refusing any disagreement a reader would call physical: the smallest index step this package’s own weakly guiding fixtures use is 0.001 in the index, which is 1.4e-3 in the permittivity, three orders above this.
- class solprop.modes.guide.ModalGuide(field_provider, mode_collection, regions, sampling=None)[source]#
Bases:
DisplayedAssociate a vector provider with the existing ordered mode collection.
- Attributes:
- field_providerModalProvider
Internal-convention full-vector field provider.
- mode_collectionModeCollection
Physical modes and their channel labels in matching order.
- regionstuple of MaterialRegion
Ordered material regions owned by the guide, each declaring its own response (
MaterialKerr) or none, which is how a linear cladding is said to be linear.- samplingModalSampling or None
What the guide’s channels were solved on, filled by
from_provider()andNonefor a guide built from constructed modes. A guide without it can be inspected and compared against, and cannot be propagated:solprop.driver.simulation.Simulation.from_waveguide()refuses it by name, because the resolution its dense reference would be sampled at is nowhere declared.
- field_provider: ModalProvider#
- classmethod from_provider(provider, omega, *, z=0.0, nodes=None, degenerate_blocks=(), basis=None, beta_rtol=1e-06, sections=1, path_rtol=None)[source]#
Build a guide from a provider that solves its own fields.
The route for a
GradedIndexProvider, and for any provider that publishes its channels: the provider owns its sector, channel count, elements, extent and radius path, exactly asfrom_step_index()takes constructed modes, and nothing here invents a resolution. Dispersion comes through the table bridge, so each channel becomes aProviderChannelModewhose propagation constant is the provider’s own generator at the knots and whose derivatives are the dense table’s interpolant.The propagation constants are then checked back against the provider at the entrance frequency, where the table interpolates nothing, and midway across the first, the middle and the last gap between knots, where it interpolates as it will in a run; the ends are where a not-a-knot cubic is worst.
A provider whose geometry moves – a declared radius path that is not constant, or any longitudinal breakpoint – is sampled along its path as well: its z domain is cut into sections at every declared kink, each divided sections times, and every section is solved at the Chebyshev-Lobatto points of the degree its own accuracy check demands, on the provider’s own rule at each position and in its own gauge (
solprop.translate.modal_path.sample_modal_path()). The checks below are then taken at the entrance, which is the provider’s own z domain start, and each channel carries its index and area along the path (ModalPath,ProviderChannelMode.path). A guide whose geometry does not move is a snapshot at one position and is bitwise what it was.- Parameters:
- providerModalProvider
Internal-convention provider naming its channels in
identity.- omeganumpy.ndarray
At least four increasing positive frequencies in rad/s covering the band the guide will be used over, 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 a bound field reaches furthest and the power a rule discards is largest, so that the rule’stail_power_boundis a bound over the whole band and not only at the frequency it was taken.- degenerate_blockssequence of sequence of int, optional
Channel indices the provider’s own order declares degenerate, as
solprop.translate.modal_tables.sample_modal_table()takes them. Tracking rotates freely inside a declared block and matches one-to-one outside it, and the generator is rotated with the fields, so a degenerate pair – the two parities of an HE_1m channel – stays physically equivalent instead of being matched by an ordering the eigenvalues cannot resolve. The blocks are retained on the table, so the propagation-constant checks below align a fresh solve the same way. Empty by default: an arbitrary provider declares no degeneracy.- basisChannelBasis or None, optional
A declared unitary change of the channel coordinates, applied to every knot after the tracking gauge (
circular_pair()builds the circular partners of a parity pair). It must act only inside blocks named in degenerate_blocks and only on blocks whose propagation constants are equal to the rounding of the largest; both are refused by name. The guide’s channels then carry the declaration’s labels – handedness rather than parity – and itsProviderChannelModerows carry the transformed channels’ surrogate areas.Nonekeeps the provider’s own coordinates, and the guide is then bitwise what it was.- beta_rtolfloat, optional
Budget on the two propagation-constant checks, default
PROVIDER_BETA_RTOL.- sectionsint, optional
Sections each kink interval of a moving guide is divided into, positive; one by default. Ignored by nothing: a snapshot refuses any other value, because it has no path to divide.
- path_rtolfloat or None, optional
Budget on a moving guide’s held-out departure along z;
Nonetakes the frequency interpolation’s own measured profile error, floored atsolprop.translate.modal_path.PATH_RTOL_FLOOR, so that the sampling in z is never the coarser of the two. A snapshot refuses one.
- Returns:
- ModalGuide
The provider, one channel mode per declared channel in the provider’s own order, and the provider’s material regions.
- Raises:
- ModeError
If the provider names no channels, the budget is invalid, a declared basis is not admitted, a channel’s tabulated propagation constant disagrees with a fresh solve by more than beta_rtol; for a moving guide, if z is not its entrance, nodes is not a rule its provider builds, its gauge is not continuous or its path does not resolve; for a snapshot, if sections or path_rtol is declared.
- classmethod from_step_index(modes, *, core, cladding, regions=())[source]#
Build a bound provider from existing exact step-index modes.
- Parameters:
- modessequence of StepIndexMode
Ordered unique bound channels of one guide.
- corePermittivityLaw
Core law, matching the modes’ core index.
- claddingPermittivityLaw
Explicit exterior law, matching the modes’ cladding index.
- regionssequence of MaterialRegion, optional
Core/exterior response ownership; empty builds two linear regions.
- Returns:
- ModalGuide
Guide with a separate full-vector physics provider.
- property identity: Mapping[str, JsonValue][source]#
Return the provider physical identity.
- Returns:
- collections.abc.Mapping
Serializable physical model and construction metadata.
- mode_collection: ModeCollection#
- property omega_domain: tuple[float, float][source]#
Return the provider frequency interval in rad/s.
- Returns:
- tuple of float
Lower and upper angular frequency in rad/s.
- regions: tuple[MaterialRegion, ...]#
- sampling: ModalSampling | None = None#
- solve(omega, z, *, nodes)[source]#
Delegate a solve to the guide’s full-vector provider.
- Parameters:
- omegafloat
Positive angular frequency in rad/s.
- zfloat
Position in m.
- nodesModalNodeRule
Common physical nodes and material indices.
- Returns:
- ModalSample
The provider’s validated E/H sample.
- class solprop.modes.guide.ModalPath(z_edges, degree, sections, rtol, z_error, gram_departure)[source]#
Bases:
DisplayedRecord how a moving guide was sampled along z, and what that measured.
The z half of a guide’s sampling (
ModalSampling), filled byModalGuide.from_provider()for a provider whose geometry moves. The path is cut into sections – the provider’s declared kink intervals, each divided sections times – and every section is solved at the Chebyshev-Lobatto points of one degree along z, the degree the accuracy check demanded: doubled until the expansion reproduced a fresh solve at the points a doubling adds to within rtol (solprop.translate.modal_path.sample_modal_path()). A run rebuilds the same positions from this record rather than searching again.- Attributes:
- z_edgesnumpy.ndarray
Section edges in m, strictly increasing, shape
(n_edges_path,): the provider’s z domain split at its interior breakpoints and subdivided.- degreeint
The Chebyshev degree along z every section is sampled at, positive; each section holds
degree + 1positions, its two ends included.- sectionsint
How many sections each kink interval was divided into, positive.
- rtolfloat
The budget the degree was searched against, positive: by default the frequency interpolation’s own measured profile error, so that the sampling in z is never the coarser of the two.
- z_errorfloat
The measured held-out departure at the degree kept, dimensionless and non-negative: the largest relative maximum-norm departure of E, H or the generator from a fresh solve between the sampled positions.
- gram_departurefloat
The largest
norm(W - I, 2)of the expanded basis at those same held-out positions, dimensionless and non-negative.
- Raises:
- ModeError
Naming the field that is out of range.
Examples
>>> import numpy as np >>> from solprop.modes.guide import ModalPath >>> path = ModalPath(np.array([0.0, 1e-3]), 2, 1, 1e-6, 3e-7, 1e-8) >>> path.positions.round(6).tolist() [[0.0, 0.0005, 0.001]] >>> round(float(path.interpolate(np.array([0.0, 1.0, 2.0]), 2.5e-4)), 12) 0.5
- property identity: Mapping[str, JsonValue][source]#
Return what a run records of the path, and a resume compares.
The declared edges, degree, subdivision and budget exactly; the two measured departures at
MEASURED_DIGITS, as every other measured entry of a guide’s identity is.- Returns:
- collections.abc.Mapping
JSON-serialisable scalars and lists of scalars.
- interpolate(values, z)[source]#
Evaluate values sampled at the path’s positions at any position.
The barycentric form of the interpolating polynomial through one section’s Chebyshev-Lobatto samples – weights
(-1)^j, halved at the two ends – which is the same polynomial the translation layer’s Chebyshev coefficients carry, evaluated stably. A position outside the path holds the nearest end’s value, as aZProfileholds its own beyond its span: a run’s tables reach a few per cent past its end, and the geometry is not declared there.- Parameters:
- valuesnumpy.ndarray
One value per position, in
positionsorder flattened, position axis first, shape(n_intervals * (degree + 1), *value_shape).- zfloat
Position in m.
- Returns:
- numpy.ndarray
The value at z, shape
value_shape.
- Raises:
- ModeError
If values does not carry one row per position.
- property n_intervals: int[source]#
Return how many sections the path is cut into.
- Returns:
- int
The section count.
- property positions: Float[ndarray, 'n_intervals_path n_deg_path'][source]#
Return every sampled position, section by section.
- Returns:
- numpy.ndarray
Positions in m, shape
(n_intervals, degree + 1), increasing inside a section, so a position on an interior edge appears twice.
- section(z)[source]#
Return the section a position lies in, clamped to the path’s ends.
- Parameters:
- zfloat
Position in m.
- Returns:
- int
The section index; a position on an interior edge takes the section starting there, and one outside the path the nearest end’s.
- z_edges: Float[ndarray, 'n_edges_path']#
- class solprop.modes.guide.ModalSampling(omega, z, nodes, degenerate_blocks, profile_error, basis=None, path=None)[source]#
Bases:
DisplayedRecord what a guide’s channels were solved on, and what that measured.
Everything a dense reference table of this guide is rebuilt from – the knots, the position, the node rule and the declared degenerate blocks – together with the profile interpolation error those knots were measured to give. A guide built by
ModalGuide.from_provider()carries one; a guide built from constructed modes does not, and cannot be propagated, because nothing declares the resolution its reference would be sampled at.- Attributes:
- omeganumpy.ndarray
The knots the provider was solved at, increasing positive angular frequencies in rad/s, shape (n_omega_sampling,), at least four.
- zfloat
The one position in m the snapshot was taken at.
- nodesModalNodeRule
The common Cartesian rule every knot was solved on, with its own certified tail bound and material ownership.
- degenerate_blockstuple of tuple of int
Channel indices the provider’s own order declares degenerate, as the tracking took them.
- basisChannelBasis or None
The declared unitary change of channel coordinates the reference carries on top of the tracking gauge, or
Nonefor the provider’s own. It is here because it is part of what the reference is, exactly as the knots and the node rule are: a table rebuilt from this sampling without it is a different basis.- profile_errorfloat
Largest relative departure, in the maximum norm over nodes, components and channels, between the table’s frequency interpolation of E and H and a fresh provider solve, measured at the entrance knot, where the table interpolates nothing, and at the midpoints of the first, the middle and the last knot gap, where a not-a-knot cubic is worst. Non-negative and dimensionless. It is the budget
solprop.translate.modal_tables.modal_transform_tables()records on the run’s own record, and it is a measurement at four frequencies, not a proof over the band.- pathModalPath or None
How a guide whose geometry moves was sampled along z – its sections, the degree its accuracy check demanded and what that measured – or
Nonefor a snapshot. With it z is the path’s entrance and nodes the entrance rule, which the provider is asked for again at every sampled position.
- Raises:
- ModeError
If the knots are not at least four increasing positive finite values, the position is not finite, or the profile error is negative or not finite.
- basis: ChannelBasis | None = None#
- property identity: Mapping[str, JsonValue][source]#
Return what a run records of this sampling, and a resume compares.
Everything a reader needs to say what resolution the reference was built at – the knots themselves, the snapshot position, the node rule’s own construction and tail certificate, the declared degenerate blocks and the measured profile error – and no array the rule owns by value: a rule’s node coordinates and area weights are the megabytes
ModalTransformTablesalready describes by extent, and the rule’s ownidentityis a mapping of scalars (output schema 7).The profile error is measured, by a dense solve and a reduction, so it enters at
MEASURED_DIGITSsignificant digits: the same guide re-solved on another BLAS differs in the last bits of it and in nothing else, and a resume key and a reconstruction gate must not refuse a run that did not change. The knots, the position, the blocks and a declared basis are declared and enter exactly.- Returns:
- collections.abc.Mapping
JSON-serialisable scalars and lists of scalars, stable across processes.
Examples
>>> import numpy as np >>> from solprop.modes.guide import ModalSampling >>> from solprop.modes.vector import ModalNodeRule >>> rule = ModalNodeRule( ... np.zeros((1, 2)), np.ones(1), np.zeros(1, np.int32), {"rule": "demo"} ... ) >>> sampling = ModalSampling( ... np.array([1.0, 2.0, 3.0, 4.0]), 0.0, rule, ((0,),), 1e-5 ... ) >>> sampling.identity["n_knots"], sampling.identity["n_nodes"] (4, 1)
- nodes: ModalNodeRule#
- omega: Float[ndarray, 'n_omega_sampling']#
- solprop.modes.guide.PROVIDER_BETA_RTOL = 1e-06#
Largest relative disagreement a guide admits between its tables and its provider.
ModalGuide.from_provider()reads each channel’s propagation constant out of a tracked dense table and then asks the provider again, at the entrance frequency and at three frequencies the table did not sample – midway across the first, the middle and the last gap between knots. The held-out ones are what means something: they measure the frequency interpolation the table performs, which is the model the guide’s linear operator will use, and the ends of a band are where a not-a-knot cubic is worst.It certifies ``beta`` alone.
beta_1is the interpolant’s and is not checked against anything here;beta_2is uncertified and diagnostic (ProviderChannelMode).The margin is measured, with this guard’s own quantity on the reference parabolic sector of
tests/modes/test_modal_tables.py, by a development study of the provider-table derivatives. At the frequency the guard tests: 1.11e-12 over a +-2 % band with five knots, 7.03e-14 with nine, 1.96e-8 over +-20 % with four knots and 2.08e-7 over +-40 % with four. So 1e-6 has orders of margin on any band a run would use and a factor of five on the widest band measured at the middle gap. Taken over every knot midpoint, that widest case reaches 3.10e-6 at an end gap, above this budget, which is why the guard now samples the first and the last gap as well: a four-knot table over +-40 % is refused, and needs more knots.
- class solprop.modes.guide.ProviderChannelMode(table, label, outer_radius, area, path=None, path_tables=(), path_areas=(), source=None)[source]#
Bases:
DisplayedOne labelled channel of a modal provider, carrying its sampled dispersion.
The dispersion carrier the ordered
modesof aModalGuideneed for a provider that solves its own fields: its index comes from the table bridge (solprop.translate.modal_tables.sample_provider_table()), and its identity is the provider’s own channel label. It is not a second mode solver behind aModeinterface, and it has no transverse profile of its own:field()refuses, because the physical fields of this channel are the provider’s three-component E and H, not two real transverse components.What is certified and what is not.
ModalGuide.from_provider()certifiesbetaand nothing else: it compares the table’s propagation constant with a fresh provider solve at the entrance and at one held-out frequency, againstPROVIDER_BETA_RTOL.beta_1is the interpolant’s own derivative, and nothing checks it at construction; measured held-out against fresh-solve five-point differences on the reference parabolic sector it is 4.4e-13 relative at nine knots over a +-2 % band.beta_2– whatsolprop.modes.base.beta2()differences out of that derivative, and so whatsolprop.modes.base.zdw(),solprop.tools.dispersion_length()andsolprop.tools.soliton_order()are given for this channel – is diagnostic only and uncertified: on the same sector and the same held-out frequencies it is 2.8e-2 relative at nine knots, 3.2e-3 at seventeen and 2.1e-3 at thirty-three over +-2 %, and worse than unity over a +-20 % band at any knot count measured. A table’s cubic reproduces a nearly constant group delay to twelve digits and still has no useful curvature; readbeta_2here as an order of magnitude, and take a dispersion length or a soliton order from it as the same (measured by a development study of the provider-table derivatives).- Attributes:
- tablesolprop.modes.tabulated.IndexTable
Snapshot of this channel’s complex index, with the interpolant’s slopes.
- labelChannelLabel
The provider’s own label for this channel.
- outer_radiusfloat
Positive radius in m of the provider’s declared physical domain, shared by every channel of one guide, so that a collection has one cross-section.
- areafloat
Positive surrogate effective area in m² (
solprop.translate.modal_tables.channel_area()) at the median knot of the band the guide was sampled on, the middle of that band. The table’ssource_identity["channel_area_omega"]names that frequency. The frequency is part of the number: on the reference parabolic sector the lowest knot’s area is 2.0 %, 25 % and 66 % above the median knot’s over +-2 %, +-20 % and +-40 % bands. On a moving guide, the entrance’s.- pathModalPath or None
How a guide whose geometry moves was sampled along z, or
Nonefor a snapshot. With it the channel is not z-constant: its index and its surrogate area are the path’s, interpolated between the sampled positions exactly as the run’s maps are.- path_tablestuple of IndexTable
The channel’s index table at every position of path, in
ModalPath.positionsorder flattened; empty for a snapshot.- path_areastuple of float
Its surrogate area at every position of path, in m², same order; empty for a snapshot.
- sourceChannelSource or None
The provider, index and rule the channel can be solved again from, set by
ModalGuide.from_provider();Nonefor a channel built by hand, which then cannot answersolved_area(). A live handle, not configuration: it is left out of the channel’s serialised record, since the run records the provider’s identity beside its channels.
- aeff(z=0.0)[source]#
Return the declared surrogate effective area.
- Parameters:
- zfloat, optional
Position in m; ignored for a snapshot, interpolated along a moving guide’s path.
- Returns:
- float
A_effin m², the scalar surrogate ofsolprop.translate.modal_tables.channel_area()at the median knot of the sampled band (source_identity["channel_area_omega"]), not the nonlinear coefficient of this vector channel and not a function of frequency: a scalar route has one area and this is the one declared.
- area_at(omega)[source]#
Return the surrogate area at one frequency, along z for a moving guide.
solved_area()at every position the channel’s own area is sampled at: once for a snapshot, and at each position of a moving guide’spath– the positionspath_areasholds – interpolated between them exactly asarea_pathinterpolates the median-knot areas, with the same span and the same section edges. It is whatsolprop.nonlinear.gnlse.SpecifiedMode.from_mode()hands a scalar model given the pump’s wavelength.- Parameters:
- omegafloat
Positive angular frequency in rad/s, inside the provider’s domain.
- Returns:
- float or solprop.profiles.ZProfile
The area in m², a number for a snapshot and a path along z for a moving guide.
- Raises:
- ModeError
As
solved_area().
- property area_path: ZProfile | None[source]#
Return the surrogate area along a moving guide’s path, or
None.Declared under the cross-section name a moving mode declares its area by (
solprop.modes.protocol.GEOMETRY_PATH_NAMES), so that the tables of a run split on the path’s section edges, where the interpolant’s slope may jump.- Returns:
- solprop.profiles.ZProfile or None
The area in m² along z, or
Nonefor a snapshot.
- property azimuthal_order: int[source]#
Return the label’s azimuthal order, or zero where it has none.
- Returns:
- int
Azimuthal order, dimensionless.
- property channel_label: ChannelLabel[source]#
Return the provider’s own label for this channel.
- Returns:
- ChannelLabel
The label the provider published in its identity.
- dimlimits(z=0.0)[source]#
Return the provider’s declared physical domain.
- Parameters:
- zfloat, optional
Position in m; ignored, the snapshot is one position.
- Returns:
- tuple
("polar", (0, 0), (outer_radius, 2 pi))in m and rad, the same for every channel of one guide.
- dneff_domega(omega, z=0.0)[source]#
Return the frequency derivative of the effective index.
It is the interpolant’s, not an analytic model derivative (ADR-006): a provider declares no closed form, so the table carries the slopes of the dense generator interpolant and this differentiates them. Nothing certifies it:
solprop.translate.modal_tables.validate_modal_table()measures no frequency derivative, andModalGuide.from_provider()checksbetaalone. Measured held-out against fresh-solve five-point differences on the reference parabolic sector, thebeta_1it gives is 4.4e-13 relative at nine knots over a +-2 % band; the second derivative taken from it is a diagnostic (ProviderChannelMode).- Parameters:
- omeganumpy.ndarray
Positive angular frequencies in rad/s inside the table, shape (n_omega,).
- zfloat, optional
Position in m; ignored for a snapshot, interpolated along a moving guide’s path.
- Returns:
- numpy.ndarray
d n_eff / d omegain s/rad, complex, shape (n_omega,).
- entrance()[source]#
Return this channel’s snapshot at the entrance, with no path.
What a moving guide’s cumulative
Phiis carried by: the run puts one reference channel’s entrance dispersion inPhiand every channel’s departure from it, the motion included, in the coupled flow (solprop.translate.problem._general_assembly).- Returns:
- ProviderChannelMode
The same channel, z-constant, with its entrance table and area.
- field(points, z=0.0)[source]#
Refuse a two-component transverse profile this channel does not have.
- Parameters:
- pointsnumpy.ndarray
Cross-section positions, shape (n_points, 2).
- zfloat, optional
Position in m.
- Returns:
- numpy.ndarray
Never returns.
- Raises:
- ModeError
Always: the physical fields of a provider channel are the provider’s three-component E and H on its node rule, and squeezing them into two real transverse components would be a different, unqualified mode.
- label: ChannelLabel#
- neff(omega, z=0.0)[source]#
Return the tabulated complex effective index.
- Parameters:
- omeganumpy.ndarray
Positive angular frequencies in rad/s inside the table, shape (n_omega,).
- zfloat, optional
Position in m; ignored for a snapshot, interpolated along a moving guide’s path.
- Returns:
- numpy.ndarray
n_eff(omega), complex, shape (n_omega,).
- neff_minus_one(omega, z=0.0)[source]#
Return
n_eff - 1formed without cancellation.- Parameters:
- omeganumpy.ndarray
Positive angular frequencies in rad/s inside the table, shape (n_omega,).
- zfloat, optional
Position in m; ignored for a snapshot, interpolated along a moving guide’s path.
- Returns:
- numpy.ndarray
n_eff - 1, complex, shape (n_omega,), the first of the table’s refractivity pair; its second is the derivativedneff_domega()returns.
- path_tables: tuple[IndexTable, ...] = ()#
- principal_component(z=0.0)[source]#
Declare that a vector channel needs both transverse components.
- Parameters:
- zfloat, optional
Position in m; ignored, the snapshot is one position.
- Returns:
- None
Always
None.
- property scale_invariant: bool[source]#
Whether the profile scales exactly with a core radius.
- Returns:
- bool
Always
False: a provider channel has no scaling law, and its normalised frequency changes with the geometry.
- solved_area(omega, z=0.0)[source]#
Return the surrogate effective area from a fresh provider solve.
The same definition as
areaandaeff()–solprop.translate.modal_tables.channel_area(),(int |E|^2 dA)^2 / int |E|^4 dAover the three Cartesian components, the longitudinal one included – but at the frequency asked for rather than at the median knot of the sampled band, which is where a scalar model’s pump usually is not. The provider is solved at omega on the guide’s own rule: the sampling rule for a snapshot, and for a moving guide the rule the provider builds at z on the same request (solprop.translate.modal_path.path_rule()), which is the rule the path’s own samples were taken on. The declared channel basis is applied first; no tracking gauge is, so a channel inside a degenerate block that no basis declares has the provider’s own member of that block.- Parameters:
- omegafloat
Positive angular frequency in rad/s, inside the provider’s domain.
- zfloat, optional
Position in m; ignored for a snapshot, which is solved where it was sampled.
- Returns:
- float
A_effin m².
- Raises:
- ModeError
If the channel carries no
ChannelSource, or the provider refuses the frequency or the position.
- source: ChannelSource | None = None#
- table: IndexTable#
- class solprop.modes.guide.StepIndexProvider(modes, regions)[source]#
Bases:
DisplayedEvaluate exact bound step-index E/H using independent full-domain power norms.
A returned
ModalSampleis always fully valid: an unsupported channel is refused by name rather than masked out, sovalidis allTruein every sample this provider returns.- Attributes:
- modestuple of StepIndexMode
Unique physical channels of one core/cladding geometry, in state order.
- regionstuple of MaterialRegion
Core and unbounded exterior, with material laws matching the modes.
- property identity: Mapping[str, JsonValue][source]#
Return internal convention, bound-model and channel identities.
- Returns:
- collections.abc.Mapping
Serializable physical model and construction metadata.
- modes: tuple[StepIndexMode, ...]#
- node_rule(omega, z=0.0, *, n_r=None, n_theta=None)[source]#
Build a common rule and certify the discarded cladding power.
- Parameters:
- omegafloat
Positive frequency in rad/s in the guided band.
- zfloat, optional
Position in m within the radius path.
- n_rint or None, optional
Gauss nodes per radial panel, at least two;
Noneuses the 64 this provider’s exponential cladding tail needs.- n_thetaint or None, optional
Angular node count; None uses 4*(maximum n+1)+1 for quartic products.
- Returns:
- ModalNodeRule
Material-split rule with independently integrated tail bounds.
- Raises:
- ModeError
If the bound field or tail cannot be certified.
- property omega_domain: tuple[float, float][source]#
Return material frequency support; solve checks each mode cutoff.
- Returns:
- tuple of float
Lower and upper angular frequency in rad/s.
- regions: tuple[MaterialRegion, ...]#
- solve(omega, z, *, nodes)[source]#
Solve bound channels on explicitly owned physical nodes.
- Parameters:
- omegafloat
Positive angular frequency in rad/s within material and guided support.
- zfloat
Position in m in the radius path.
- nodesModalNodeRule
Common Cartesian nodes with correct core/cladding ownership.
- Returns:
- ModalSample
Unit full-domain power E/H and their sampled physical Gram matrix. Its
validmask is allTrue: an unsupported channel is refused here, not masked.
- Raises:
- ModeError
If domains, material ownership, norm or a boundary residual is invalid.
- solprop.modes.guide.circular_pair(labels, block)[source]#
Declare the circular partners of one exactly degenerate parity pair.
The change of basis that turns the real
cos/sinpair of an azimuthally degenerate family into its two circular partners, which is the basis an isotropic Kerr tensor is diagonal in: for a field that is circular at every nodeE . Evanishes, so(E . E) E*drops out ofP = eps0 chi3 (2 |E|^2 E + (E . E) E*) / 4and the self coefficient falls to two thirds of the linear one.The convention, derived here. The even partner is the
cos((n-1) theta)pattern about x, which forn = 1is the x-polarised member, and the odd one is its quarter-turn partner about y (ChannelLabel). A provider’s fields are in the internal convention ofdocs/dev/conventions.mdsection 2, whose plane wave isexp(i(omega t - beta z)), so the physical transverse field of a channel with nodal rows(e_even, e_odd)and amplitude one isRe[(e_even + a e_odd) exp(i omega t)]. Witha = -ithat is(cos(omega t), sin(omega t))in(x, y): the vector turns from x towards y, which an observer looking towards the source sees turn anticlockwise. That is positive helicity, which optics calls left circular (Jackson, Classical Electrodynamics, section 7.2), and its optics-convention Jones vector is the conjugate(1, i)/sqrt(2), whichsolprop.polarisationrecords as positive StokesV. Soe_left = (e_even - i e_odd)/sqrt(2),e_right = (e_even + i e_odd)/sqrt(2)and the two columns of the returned matrix are exactly those, in that order:
block[0]becomes the left channel andblock[1]the right one. The order is not free –block[0]must be the even member andblock[1]the odd one, and a block given the other way round is refused by name. The names left and right are a convention, fixed by the derivation above, not a derived fact.- Parameters:
- labelscollections.abc.Sequence
Every channel’s label, in the guide’s own row order.
- blockcollections.abc.Sequence
Exactly two channel indices, one labelled
"even"and one"odd", of the same family and quantum numbers.
- Returns:
- ChannelBasis
The declaration, with
gauge_id"circular(i,j)"for the block’s own two indices in ascending order and the two labels relabelled by handedness.
- Raises:
- solprop.exceptions.ModeError
If the block is not two distinct valid indices, or the two labels are not a parity pair of one family.
Examples
>>> import numpy as np >>> from solprop.modes.guide import circular_pair >>> from solprop.modes.protocol import ChannelLabel >>> pair = ( ... ChannelLabel("HE", 1, 1, "even", None, "HE1,1:even"), ... ChannelLabel("HE", 1, 1, "odd", None, "HE1,1:odd"), ... ) >>> basis = circular_pair(pair, (0, 1)) >>> bool(np.allclose(basis.transform[:, 0], [1, -1j] / np.sqrt(2))) True >>> basis.labels[1].handedness 'right'
- solprop.modes.guide.provider_channels(provider)[source]#
Return a provider’s declared channels as labels, in its own order.
A provider publishes its channels as mappings inside
identity, which is what survives JSON; this is the one place they are turned back intoChannelLabelobjects for a caller that has to name one – to declare a change of basis over a pair, most of all.- Parameters:
- providersolprop.modes.vector.ModalProvider
A provider that names its channels.
- Returns:
- tuple
One label per channel.
- Raises:
- ModeError
If the provider names no channels.
- solprop.modes.guide.radial_node_rule(interfaces, outer_radius, *, n_r, n_theta)[source]#
Build interface-split Cartesian nodes from the existing polar panel rule.
- Parameters:
- interfacessequence of float
Strictly increasing positive material radii in m, inside outer_radius.
- outer_radiusfloat
Positive finite truncation radius in m; tail certification is separate.
- n_rint
At least two Gauss nodes in each radial panel.
- n_thetaint
At least two angular nodes; caller certifies angular resolution.
- Returns:
- ModalNodeRule
Positive area weights and one int32 material index per node.
- Raises:
- ModeError
If the radius or interface arrangement is invalid.