solprop.modes.graded_index#

Solve radial TE/TM Maxwell sectors and a separately labelled LP approximation.

The symmetric variational problem uses continuous linear elements on a declared radial domain matched to an exact decaying exterior. Hybrid HE/EH sectors are refused: TE/TM evidence cannot qualify hybrid GRIN. The exterior material is explicit and the truncation radius is refined independently of the radial mesh.

Module Attributes

MAX_EXTERIOR_TAIL_FRACTION

Largest share of a channel's power admitted beyond the matching radius.

Classes

GradedIndexProvider(*, permittivity, ...[, ...])

Solve bound radial vector sectors on an explicitly refined finite domain.

PowerLawPermittivity(centre, contrast[, ...])

Describe a finite radial power-law permittivity profile.

RadialPermittivity(*args, **kwargs)

Evaluate a radial material law independently of a longitudinal path.

class solprop.modes.graded_index.GradedIndexProvider(*, permittivity, exterior, radius, family, count, elements, extent, azimuthal_order=0, core_responses=())[source]#

Bases: Displayed

Solve bound radial vector sectors on an explicitly refined finite domain.

A channel too close to cutoff for the declared finite domain is refused by name: more than MAX_EXTERIOR_TAIL_FRACTION of its power beyond the matching radius means the exact exterior continuation, not the discretised core, carries the mode. A returned ModalSample is therefore always fully valid – valid is all True – because an unsupported channel is refused rather than masked.

Attributes:
permittivityRadialPermittivity

Core law without a z argument, evaluated at the local radial fraction.

exteriorPermittivityLaw

Homogeneous exterior material law, independently declared.

radiusfloat or ZProfile

Positive core radius in m; all z dependence follows this named path.

family{‘TE’, ‘TM’, ‘LP’, ‘HE’, ‘EH’}

TE0/TM0 Maxwell sector or explicitly weak-guidance scalar LP approximation. Only TE, TM and LP are supported. HE and EH stay in the type so that construction reaches the refusal and can be tested there; passing either raises rather than silently solving a hybrid sector.

countint

Number of consecutive radial channels, at least one.

elementsint

At least eight radial elements per core radius; must be refined.

extentfloat

Dimensionless matching radius, greater than one; exact Bessel exterior.

azimuthal_orderint

Zero for TE/TM; non-negative scalar angular order for LP.

core_responsestuple

The core region’s own declared physical responses, at most one of each kind, exactly as solprop.modes.material.MaterialRegion takes them. Empty, the default, declares a linear guide: its nodes carry no polarisation, which is what a linear self-imaging reference wants. The exterior is always linear, because the radial sectors this provider solves are bound and their exterior is the exact Bessel continuation – what little field is there is the tail MAX_EXTERIOR_TAIL_FRACTION already bounds, and a response declared on it would be evaluated at nodes the rule barely resolves.

at_parameters(values)[source]#

Freeze the declared radius path at explicit parameter values.

Parameters:
valuescollections.abc.Mapping

Any subset of core_radius, in m and strictly positive. A name left out keeps its path; a name this provider does not declare is an error.

Returns:
GradedIndexProvider

The same physical law and discretisation with a constant radius.

Raises:
ModeError

If a name is unknown or the radius is invalid.

azimuthal_order: int = 0#
property channel_labels: tuple[ChannelLabel, ...][source]#

Return one label per requested radial channel, in solve order.

Returns:
tuple of ChannelLabel

The sector’s family and azimuthal order with radial order 1..count, in the order solve() returns the channels, which is decreasing propagation constant. A radial sector has no parity partner and no handedness, so both are None.

Examples

>>> from solprop.modes import GradedIndexProvider, IndexMaterial
>>> from solprop.modes import PowerLawPermittivity
>>> provider = GradedIndexProvider(
...     permittivity=PowerLawPermittivity(IndexMaterial(1.45), 0.005),
...     exterior=IndexMaterial(1.44),
...     radius=25e-6,
...     family="TE",
...     count=2,
...     elements=8,
...     extent=2.0,
... )
>>> [label.display_label for label in provider.channel_labels]
['TE0,1', 'TE0,2']
core_responses: tuple[MaterialResponse, ...] = ()#
count: int#
elements: int#
extent: float#
exterior: PermittivityLaw#
family: Literal['TE', 'TM', 'LP', 'HE', 'EH']#
property identity: Mapping[str, JsonValue][source]#

Return physical law, sector and explicit discretisation identity.

Returns:
collections.abc.Mapping

Serializable physical model and construction metadata.

node_rule(omega, z=0.0, *, n_r=None, n_theta=None)[source]#

Build nodes split at the material interface and every finite element.

The frequency is not a discretisation choice here – the mesh is the declared element count over the declared extent – but it is what the discarded power beyond the matching radius depends on, so the rule certifies that tail at this frequency, as solprop.modes.guide.StepIndexProvider.node_rule() does. One signature for every provider.

That certificate costs a solve. Building the mesh is 0.22 ms on the reference parabolic sector (32 elements, two channels, on an Apple-silicon laptop) and the whole call is 4.0 ms, because the tail bound needs the channels’ fields. It is construction-time work, once per guide rather than once per step, but a caller that builds a rule per frequency knot is paying for a solve per knot and should pass one rule instead.

Parameters:
omegafloat

Positive angular frequency in rad/s in the bound band; the rule’s tail bound is certified there.

zfloat, optional

Position in m in the radius path.

n_rint or None, optional

Gauss nodes per element, at least two; None uses the four a P1 element mesh of the declared element count needs.

n_thetaint or None, optional

Angular nodes, at least two; None takes two for an LP sector of azimuthal order zero, whose fields do not depend on the angle, so that two equally spaced nodes integrate every product exactly; nine for a TE/TM sector – its Cartesian components carry one angular harmonic, whose quartic products need five – and 4 * (order + 1) + 1 where a higher LP angular order needs more.

Returns:
ModalNodeRule

Core/exterior ownership on the declared finite domain, with the exactly integrated discarded-power fraction of each channel in its identity.

Raises:
ModeError

If the frequency, the position or a requested channel is unusable.

property omega_domain: tuple[float, float][source]#

Return the common material frequency interval in rad/s.

Returns:
tuple of float

Lower and upper angular frequency in rad/s.

property parameter_paths: Mapping[str, ZProfile][source]#

Return the declared core-radius path in m.

Returns:
collections.abc.Mapping

The core_radius path in m, named as every other ParameterisedMode names it.

permittivity: RadialPermittivity#
radius: float | ZProfile#
property regions: tuple[MaterialRegion, ...][source]#

Return radial-core ownership and the homogeneous exterior.

Returns:
tuple of MaterialRegion

Ordered core and unbounded exterior ownership.

solve(omega, z, *, nodes)[source]#

Evaluate a variational vector sector or labelled LP approximation.

Parameters:
omegafloat

Positive angular frequency in rad/s in the bound band.

zfloat

Position in m in the declared radius path.

nodesModalNodeRule

Cartesian nodes within the declared finite domain.

Returns:
ModalSample

Poynting-normalised E/H; boundary_residual includes the one-sided Maxwell interface trace defect. Spatial discretisation and exterior truncation need refinement. Its valid mask is all True: a channel too near cutoff is refused by name, not masked.

Raises:
ModeError

If nodes, ownership or requested bound channels are invalid.

tail_bound(omega, z=0.0, *, outer_radius)[source]#

Return exact omitted exterior power fractions for the variational modes.

Parameters:
omegafloat

Positive angular frequency in rad/s within the bound band.

zfloat, optional

Position in m in the declared radius path.

outer_radiusfloat

Finite radius in m, at or beyond the Bessel matching radius.

Returns:
numpy.ndarray

Per-channel discarded power fractions, shape (n_modes,). These certify exterior quadrature truncation, not finite-element discretisation.

Raises:
ModeError

If the requested tail begins inside the finite-element domain.

property z_breakpoints: tuple[float, ...][source]#

Return physical radius-path breakpoints in m.

Returns:
tuple of float

Positions of physical path kinks in m.

property z_domain: tuple[float, float][source]#

Return the radius-path interval in m.

Returns:
tuple of float

Lower and upper position in m.

solprop.modes.graded_index.MAX_EXTERIOR_TAIL_FRACTION = 0.0001#

Largest share of a channel’s power admitted beyond the matching radius.

The near-cutoff guard for the radial provider, the counterpart of solprop.modes.stepindex.PROFILE_MIN_W. It is written as a power fraction rather than as the dimensionless decay gamma * extent * radius because that decay mixes the physics with the caller’s numerical choice of extent: the same mode has gamma * extent * radius of 26.5, 35.3 and 44.1 at extent 1.5, 2 and 2.5. The fraction is the quantity that says how much of the mode the exact exterior continuation has to carry, and the provider evaluates it exactly, from the analytic modified-Bessel tail integral over the same full-domain norm the fields are normalised by.

Measured at 0.0001: over every reference case of the radial-provider qualification – both sectors, both radial laws, 24 to 96 elements, matching radii 1.5/2/2.5 and contrasts 0.001/0.005/0.0098 – the largest fraction is 6.57e-6, a factor 15 below the floor, and the weakest reference channel is the second LP channel at contrast 0.001. A five-channel TE case at 48 elements puts 5.97e-8 of its last channel outside the matching radius at a 25.00 um core and 3.24e-4 at 22.98 um, so the floor refuses that channel with a factor 3.2 of margin.

The value is a judgement, not a derived bound: it is kept at 1e-4 pending verification against an observable – an overlap or a propagation constant of a near-cutoff channel converged in the matching radius – which none of those reference cases supplies. Until then a refusal near this floor is a prompt to widen extent, not a physical statement about the channel.

class solprop.modes.graded_index.PowerLawPermittivity(centre, contrast, exponent=2.0)[source]#

Bases: object

Describe a finite radial power-law permittivity profile.

Attributes:
centrePermittivityLaw

Material law at the axis; its relative permittivity multiplies the shape.

contrastfloat

Dimensionless contrast Delta in (0, 0.5); eps=eps_axis*(1-2 Delta rho**p).

exponentfloat

Shape exponent p>=2; p=2 is parabolic and p=4 is quartic.

centre: PermittivityLaw#
contrast: float#
exponent: float = 2.0#
property identity: Mapping[str, JsonValue][source]#

Return the declared centre, contrast and radial exponent.

Returns:
collections.abc.Mapping

Serializable physical model and construction metadata.

property material: PermittivityLaw[source]#

Return the material owning core responses.

Returns:
PermittivityLaw

Material law owning the core response declarations.

property omega_domain: tuple[float, float][source]#

Return the centre material’s frequency interval in rad/s.

Returns:
tuple of float

Lower and upper angular frequency in rad/s.

permittivity(omega, radial_fraction)[source]#

Evaluate the radial relative permittivity.

Parameters:
omegafloat

Positive angular frequency in rad/s within the centre material’s domain.

radial_fractionnumpy.ndarray

Dimensionless core positions in [0, 1], shape (n_points,).

Returns:
numpy.ndarray

Complex relative permittivity, shape (n_points,).

Raises:
ModeError

If a core position is non-finite or outside [0, 1].

class solprop.modes.graded_index.RadialPermittivity(*args, **kwargs)[source]#

Bases: Protocol

Evaluate a radial material law independently of a longitudinal path.

property identity: Mapping[str, JsonValue][source]#

Return the law’s physical and approximation identity.

property material: PermittivityLaw[source]#

Return the material owning core responses.

property omega_domain: tuple[float, float][source]#

Return the material frequency interval in rad/s.

permittivity(omega, radial_fraction)[source]#

Evaluate the core’s relative permittivity.

Parameters:
omegafloat

Positive angular frequency in rad/s.

radial_fractionnumpy.ndarray

Dimensionless radius divided by the declared core radius, in [0, 1].

Returns:
numpy.ndarray

Relative permittivity, shape (n_points,), in the optics convention.