solprop.coupling.models#

The coupling models of SEP 31 section 4.3 and the scalar mode profiles they use.

CouplingModel selects one of three models and carries its approximation budget:

same_basis

Exact coordinate recovery in a checked common reference: a copy for an identical basis, a declared invertible BasisChange, or the flux-pairing recovery \(\mathbf a = F^{-1}\mathbf B\) of a general modal plane into a port of the same guide. Not an interface calculation, so it takes no budget.

scalar_paraxial

The weighted least-squares fit \(G = S^\dagger M S\), \(\mathbf a = G^{-1}S^\dagger M\mathbf f\) of a scalar field onto scalar target profiles in the common paraxial measure \(M_0 = Y_0\,dA/2\), \(Y_0 = n_{\rm ref}\varepsilon_0c\), followed by the conversion to the target’s native true-power coefficients \(b = \sqrt{\operatorname{Re} n_{\rm eff}}\,a\).

vector_paraxial

The flux-pairing recovery \(\mathbf a = F^{-1}\mathbf B\) onto a general modal provider’s E/H at its nodes, with the incident H or the forward closure \(\mathbf H_t = Y_0\hat{\mathbf z}\times\mathbf E_t\).

ScalarModeBasis is the scalar transverse profile set the scalar model uses: the capillary \(HE_{1m}\) family, \(J_0(u_m r/a)\) inside the core and zero outside (SEP 31 section 4.4), and the weak-guidance \(LP_{0m}\) family of a step-index fibre, \(J_0(Ur/a)\) inside and \(J_0(U)K_0(Wr/a)/K_0(W)\) outside (Snyder and Love 1983, section 14-3; Gloge 1971). Each profile is normalised to unit area integral of its square, and its order-zero Hankel transform is evaluated in closed form, so an overlap can be taken in a plane’s own spectral quadrature without sampling a profile’s kink.

The manual page is docs/guide/workflows/coupling.md.

Module Attributes

ModelKind

A coupling model of SEP 31 section 4.3.

MODEL_KINDS

Every ModelKind, in a fixed order.

ProfileFamily

The scalar profile family of a ScalarModeBasis.

Functions

cone_angle(model_error)

Return the admitted cone half-angle \(\theta_*\) of a model budget.

radial_rule(breaks, outer, *, panel)

Return a composite Gauss--Legendre rule for int f(r) 2 pi r dr.

Classes

BasisChange(matrix, source, target)

A declared invertible change of modal basis or gauge for same_basis.

CouplingModel(kind[, model_error, basis_change])

Select a coupling model of SEP 31 section 4.3 and its approximation budget.

ScalarModeBasis(family, labels, radius, u, ...)

Scalar transverse profiles of an ordinary mode set, bound to a temporal grid.

class solprop.coupling.models.BasisChange(matrix, source, target)[source]#

Bases: object

A declared invertible change of modal basis or gauge for same_basis.

The target coefficients are matrix @ b per bin and polarisation. The change must preserve energy between the plane’s metric and the port’s, T^H Q_B T = Q_A, to solprop.coupling.port.METRIC_TOLERANCE relative; it relabels the same physical field and is refused otherwise.

Attributes:
matrixnumpy.ndarray

Optics-convention map from the plane’s columns to the port’s, shape (n_modes_target, n_modes_source) or with a trailing n_omega axis for a frequency-dependent gauge, dimensionless.

sourcestr

The plane’s basis identity the change was declared for.

targetstr

The port’s basis identity.

Raises:
CouplingError

If the matrix is not finite and two- or three-dimensional.

at(n_omega)[source]#

Return the matrix at every bin, frequency axis first.

Parameters:
n_omegaint

The number of bins.

Returns:
numpy.ndarray

Shape (n_omega, n_modes_target, n_modes_source).

Raises:
CouplingError

If a frequency-dependent matrix has another number of bins.

matrix: Complex[ndarray, 'n_modes_target n_modes_source *n_omega']#
source: str#
target: str#
class solprop.coupling.models.CouplingModel(kind, model_error=None, basis_change=None)[source]#

Bases: object

Select a coupling model of SEP 31 section 4.3 and its approximation budget.

Attributes:
kind{‘same_basis’, ‘scalar_paraxial’, ‘vector_paraxial’}

The model.

model_errorfloat or None, optional

The dimensionless energy budget \(\epsilon_m\) in (0, 1) that the approximation’s defects, angular tails, closure, passivity excess and numerical errors together must fit; required by the two approximate models, refused by same_basis, which approximates nothing. It is separate from numerical accuracy and is not a coupling-efficiency floor.

basis_changeBasisChange or None, optional

A declared invertible change of basis or gauge, same_basis only.

Raises:
CouplingError

If a budget is missing, outside (0, 1) or given to same_basis, or a basis change is given to an approximate model.

Examples

>>> CouplingModel("scalar_paraxial", model_error=0.05)
CouplingModel(kind='scalar_paraxial', model_error=0.05, basis_change=None)
basis_change: BasisChange | None = None#
property cone: float[source]#

Return the admitted cone half-angle of the budget.

Returns:
float

cone_angle() of model_error, rad.

Raises:
CouplingError

For same_basis, which has no budget.

kind: Literal['same_basis', 'scalar_paraxial', 'vector_paraxial']#
model_error: float | None = None#
solprop.coupling.models.MODEL_KINDS: tuple[Literal['same_basis', 'scalar_paraxial', 'vector_paraxial'], ...] = ('same_basis', 'scalar_paraxial', 'vector_paraxial')#

Every ModelKind, in a fixed order.

solprop.coupling.models.ModelKind#

A coupling model of SEP 31 section 4.3.

alias of Literal[‘same_basis’, ‘scalar_paraxial’, ‘vector_paraxial’]

solprop.coupling.models.ProfileFamily#

The scalar profile family of a ScalarModeBasis.

alias of Literal[‘capillary’, ‘step_index’]

class solprop.coupling.models.ScalarModeBasis(family, labels, radius, u, w, jones, effective_index, core_index=None, cladding_index=None, wall_term=None, identity='')[source]#

Bases: object

Scalar transverse profiles of an ordinary mode set, bound to a temporal grid.

Each channel is a profile \(v_m(r)\) normalised to \(\int|v_m|^2dA = 1\), times a real unit Jones vector. The guide’s own vacuum-normalised field per internal amplitude is \(\sqrt{2/(\varepsilon_0c)}\,v_m\), so a true-power coefficient \(b_m = \sqrt{\operatorname{Re}n_{{\rm eff},m}}a_m\) carries the field \(\sqrt{2/(\varepsilon_0c\operatorname{Re}n_{{\rm eff},m})}\,b_m v_m\) (docs/dev/conventions.md section 4). The profiles are those of the run’s own basis: a capillary’s do not depend on frequency, and a step-index mode’s are frozen at its declared profile wavelength exactly as the run’s are.

Build one with from_modes().

Attributes:
family{‘capillary’, ‘step_index’}

The profile family.

labelstuple of str

Channel labels, in column order.

radiusfloat

Core radius at the plane, m.

unumpy.ndarray

Dimensionless core wavenumber of each profile, shape (n_modes,): the J0 zero of a capillary HE1m mode, U of an LP0m mode.

wnumpy.ndarray

Dimensionless cladding decay W of each LP0m profile, shape (n_modes,); zero for a capillary, whose profile vanishes outside the core.

jonesnumpy.ndarray

Real unit Jones vector (x, y) of each channel, shape (n_modes, 2).

effective_indexnumpy.ndarray

\(\operatorname{Re}n_{\rm eff}\) of each channel at each bin, shape (n_modes, n_omega); zero on bins outside the active positive band.

core_indexnumpy.ndarray or None, optional

Real core index at each bin, shape (n_omega,): a capillary’s gas, a step-index fibre’s core; zero off the active positive band.

cladding_indexnumpy.ndarray or None, optional

Real cladding index at each bin of a step-index basis, shape (n_omega,).

wall_termnumpy.ndarray or None, optional

\(|\nu_n|\) of a capillary’s HE modes at each bin, shape (n_omega,): the Marcatili wall term \(\nu_n = (\varepsilon + 1)/(2\sqrt{\varepsilon - 1})\) of the wall permittivity \(\varepsilon\); zero off the active positive band. It sets the leaky field’s departure from the scalar profile (leaky()).

identitystr, optional

A short description of the source modes, for reports.

Raises:
CouplingError

If the arrays disagree in shape.

cladding_index: Float[ndarray, 'n_omega'] | None = None#
cone_gram(kappa_star)[source]#

Return the Gram of the profiles’ angular content inside a cone.

\(\int_{|\boldsymbol\kappa|<\kappa_*}\tilde V_m\tilde V_n d^2\kappa/(2\pi)^2 = \int_0^{\kappa_*}\tilde v_m\tilde v_n\,2\pi\kappa\,d\kappa\), by composite Gauss–Legendre panels of at most half a period of \(J_0(\kappa a)\). The tail outside the cone is the whole-plane gram() minus this.

Parameters:
kappa_starfloat

The cone’s transverse wavenumber, rad/m.

Returns:
numpy.ndarray

Real symmetric, dimensionless, shape (n_modes, n_modes).

core_index: Float[ndarray, 'n_omega'] | None = None#
effective_index: Float[ndarray, 'n_modes n_omega']#
family: Literal['capillary', 'step_index']#
classmethod from_modes(modes, grid, z=0.0)[source]#

Return the scalar profiles of an ordinary mode set at a plane.

Admitted are a capillary’s \(HE_{1m}\) modes (Marcatili; the scalar J0 profile is exactly their transverse field) and a step-index fibre’s \(HE_{1m}\) modes taken in the weak-guidance limit as \(LP_{0m}\). Every other family has angular structure the scalar model cannot hold and is refused by name.

Parameters:
modessequence of solprop.modes.protocol.Mode

The run’s modes, in column order.

gridsolprop.grid.TimeGrid

The run’s temporal grid, on whose active positive bins the effective indices are evaluated.

zfloat, optional

The plane’s position along the guide, m.

Returns:
ScalarModeBasis

The profiles.

Raises:
CouplingError

For an empty set, mixed or unsupported families, or modes of different radii.

gram()[source]#

Return the exact whole-plane Gram of the normalised profiles.

Distinct profiles of one family are orthogonal (different zeros of the core’s eigenvalue equation); the same profile carried by two channels, the two polarisations of one mode, overlaps itself fully, and their Jones vectors make the channels orthogonal.

Returns:
numpy.ndarray

One where two profiles are the same and zero otherwise, shape (n_modes, n_modes).

hankel(kappa)[source]#

Return each normalised profile’s order-zero Hankel transform.

\(\tilde v_m(\kappa) = \int_0^\infty v_m(r)J_0(\kappa r)r\,dr\), the convention of solprop.hankel, in closed form; the two-dimensional Fourier transform of the axisymmetric profile is \(2\pi\tilde v_m\).

Parameters:
kappanumpy.ndarray

Transverse wavenumbers, rad/m, non-negative, shape (n_k,).

Returns:
numpy.ndarray

In m, shape (n_modes, n_k).

identity: str = ''#
jones: Float[ndarray, 'n_modes 2']#
labels: tuple[str, ...]#
leaky(omega)[source]#

Return a capillary’s leaky-field parameter at each bin.

Marcatili’s leaky HE1m core field is \(J_0(u_m(1 - i\delta)r/a)\) with \(\delta = \nu_n c/(\omega a)\), where the scalar profile has \(\delta = 0\) (Marcatili and Schmeltzer 1964, doi:10.1002/j.1538-7305.1964.tb04108.x). Its energy-weighted mean over a coupling’s occupied bins is the range parameter of CAPILLARY_ENVELOPE.

Parameters:
omeganumpy.ndarray

Absolute angular frequency of each bin, rad/s, shape (n_omega,).

Returns:
numpy.ndarray or None

\(|\nu_n|c/(\omega a)\), dimensionless, shape (n_omega,), zero off the active positive band; None for a basis without a wall term (a step-index one).

property n_modes: int[source]#

Return the number of channels.

Returns:
int

len(labels).

outer()[source]#

Return the radius beyond which every profile is negligible, m.

Returns:
float

The core radius for a capillary; for an LP set, where the slowest cladding tail has decayed by 39 e-foldings, log(1e17).

radius: float#
u: Float[ndarray, 'n_modes']#
values(r)[source]#

Return each normalised profile at radii.

Parameters:
rnumpy.ndarray

Non-negative radii, m, shape (n_r,).

Returns:
numpy.ndarray

\(v_m(r)\) in 1/m, shape (n_modes, n_r).

w: Float[ndarray, 'n_modes']#
wall_term: Float[ndarray, 'n_omega'] | None = None#
solprop.coupling.models.cone_angle(model_error)[source]#

Return the admitted cone half-angle \(\theta_*\) of a model budget.

SEP 31 sections 4.3 and 4.6: \(1 - \cos\theta_* = \epsilon_m/8\).

Parameters:
model_errorfloat

The budget \(\epsilon_m\), in (0, 1).

Returns:
float

\(\theta_*\) in rad.

Examples

>>> f"{cone_angle(0.08):.6f}"
'0.141539'
solprop.coupling.models.radial_rule(breaks, outer, *, panel)[source]#

Return a composite Gauss–Legendre rule for int f(r) 2 pi r dr.

Panels break at each of breaks (a profile’s derivative jumps there) and are at most panel long, so each holds a smooth piece of the integrand.

Parameters:
breakssequence of float

Interior radii where the integrand is not smooth, m.

outerfloat

The rule’s outer radius, m.

panelfloat

Largest panel length, m.

Returns:
tuple of numpy.ndarray

Nodes in m and area weights in m^2 (the Jacobian 2 pi r included), each shape (n_r,).

Examples

>>> import numpy as np
>>> r, w = radial_rule([1.0], 2.0, panel=0.5)
>>> f"{np.sum(w):.12f}"  # the area of the disc of radius 2
'12.566370614359'