solprop.modes.collection#

Ordered, validated sets of modes, and the overlaps between them.

A multimode simulation propagates a set of modes through one transverse quadrature rule and one pair of transform matrices, which only works if they share a cross-section. ModeCollection is that set: ordered, because the mode axis of the kernel state is, and validated once at construction rather than at every use.

It also answers the two questions the translation layer asks of a mode set – whether the azimuthal coordinate has to be resolved, and to what order – and offers the overlaps that say whether the set is orthonormal at all.

overlap() integrates adaptively with scipy.integrate.dblquad(), deliberately not on the fixed rule. The fixed rule’s orthonormality is the thing being checked in tests/modes/test_quadrature.py; if the check used the same rule it would be true by construction and would say nothing.

Overlaps use the package normalisation (docs/dev/conventions.md section 4),

\[\langle m_1 | m_2 \rangle = \tfrac12 \sqrt{\varepsilon_0/\mu_0} \int e_1^* \cdot e_2 \, \mathrm{d}A \Big/ \sqrt{N_1 N_2},\]

so a mode overlaps itself by one. For a slab set (solprop.modes.slab) the integral is over x alone, per metre of y, with scipy.integrate.quad().

Functions

orthonormality(modes[, z])

Return the matrix of normalised overlaps of a mode set.

overlap(first, second[, z])

Return the normalised overlap integral of two modes.

polarisation_pairs(modes)

Expand a mode set so that each degenerate mode carries both polarisations.

transverse_measure(modes)

Return what a mode set's transverse integrals measure, refusing a mixture.

Classes

ModeCollection(modes)

An ordered set of modes sharing one cross-section.

class solprop.modes.collection.ModeCollection(modes)[source]#

Bases: Displayed, Sequence[Mode]

An ordered set of modes sharing one cross-section.

Validated once, at construction: the mode axis of the kernel state is positional, and one transform matrix serves the whole set, so a set whose members disagree about the geometry is not a set.

Attributes:
modessequence of Mode

The modes, in the order the mode axis will carry them. Any sequence is accepted and is stored as a tuple, so a collection cannot be changed after it has been validated.

property azimuthal_order: int[source]#

Return the highest azimuthal order in the set.

Returns:
int

The bound that fixes how many azimuthal nodes a quartic product needs, n_theta >= 4 h + 1.

property channel_labels: tuple[ChannelLabel, ...][source]#

Return each member’s physical label, in the order of the mode axis.

Returns:
tuple of ChannelLabel

The members’ own solprop.modes.protocol.Mode.channel_label, in order. 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, exactly as its handedness is. Output schema 7 persists these labels.

Examples

>>> from solprop.modes import MarcatiliMode
>>> from solprop.modes.collection import ModeCollection
>>> modes = ModeCollection([MarcatiliMode(125e-6, "He", 1.0)])
>>> [label.display_label for label in modes.channel_labels]
['HE1,1:even']
dimlimits(z=0.0)[source]#

Return the cross-section the whole set shares.

Parameters:
zfloat, optional

Position along the waveguide, m.

Returns:
tuple

As solprop.modes.protocol.Mode.dimlimits().

Raises:
ModeError

If the modes disagree at this position, which a taper of one mode but not another would cause.

Examples

>>> from solprop.modes import MarcatiliMode
>>> from solprop.modes.collection import ModeCollection
>>> ModeCollection([MarcatiliMode(125e-6, "He", 1.0)]).dimlimits()[0]
'polar'
classmethod he1m(radius, gas, pressure, count, *, model='full', loss=True, cladding=None, temperature=293.15, susceptibility=None)[source]#

Build the first count HE_1m modes of one gas-filled capillary.

The linearly polarised hybrid modes of a hollow capillary, in the order of their radial index. They share a cross-section by construction, their azimuthal order is zero, so no azimuthal quadrature is needed, and they are the set a capillary run uses unless told otherwise.

Every keyword after count is MarcatiliMode’s own and is given to each mode unchanged, so a set can carry a test-only cladding or gas susceptibility the way a single mode can.

Parameters:
radiusfloat or ZProfile

Core radius, m, constant or along z.

gasGas or str

Filling gas.

pressurefloat or ZProfile

Fill pressure, bar, constant or along z.

countint

How many modes, at least one; the modes are HE_11 to HE_1count.

model{‘full’, ‘reduced’}, optional

Which Marcatili model the modes use.

lossbool, float or callable, optional

Whether to include propagation loss, or the loss to use.

claddingGlassIndex, complex or None, optional

The cladding index; None is the package default silica.

temperaturefloat or ZProfile, optional

Gas temperature, K, or a path of it along z.

susceptibilityGasSusceptibility or None, optional

A susceptibility to use in place of the built-in gas’s.

Returns:
ModeCollection

The validated set, in radial order.

Raises:
ModeError

If count is below one.

Examples

>>> from solprop.modes import ModeCollection
>>> modes = ModeCollection.he1m(125e-6, "He", 0.8, 4)
>>> len(modes), modes[3].m, modes.azimuthal_order
(4, 4, 0)
classmethod marcatili(radius, gas, pressure, kinds, n, m, *, model='full', loss=True, cladding=None, temperature=293.15, susceptibility=None)[source]#

Build an ordered mixture of hybrid and transverse capillary modes.

Parameters:
radiusfloat or ZProfile

Core radius, m, constant or along z.

gasGas or str

Filling gas.

pressurefloat or ZProfile

Fill pressure, bar, constant or along z.

kindssequence of {‘HE’, ‘TE’, ‘TM’}

Family of each mode, non-empty.

nsequence of int

Azimuthal indices, one per family; nonzero for HE and zero for TE/TM.

msequence of int

Positive radial indices, one per family.

model{‘full’, ‘reduced’}, optional

Marcatili approximation used by every mode.

lossbool, float or callable, optional

Propagation-loss specification passed to every mode.

claddingGlassIndex, complex or None, optional

Cladding index; None selects the package default silica.

temperaturefloat or ZProfile, optional

Gas temperature, K, positive, or a path of it along z.

susceptibilityGasSusceptibility or None, optional

Explicit susceptibility in place of the selected fit.

Returns:
ModeCollection

Modes in the input order, all with phi = 0. Use with_polarisations() to add degenerate partners.

Raises:
ModeError

If the index sequences are empty, have different lengths, or describe an invalid mode.

Examples

>>> from solprop.modes import ModeCollection
>>> modes = ModeCollection.marcatili(
...     125e-6, "He", 1.0, ["HE", "HE", "TM"], [1, 2, 0], [1, 1, 1]
... )
>>> len(modes), modes.azimuthal_order
(3, 1)
modes: Sequence[Mode]#
property needs_full_rule: bool[source]#

Return whether the azimuthal coordinate has to be resolved.

Returns:
bool

False for a set whose products are azimuthally symmetric – any set of HE_1m modes – which then needs the radial rule alone, and for a slab set, which is uniform in y; True otherwise.

orthonormality(z=0.0)[source]#

Return the matrix of normalised overlaps of the set.

Parameters:
zfloat, optional

Position along the waveguide, m.

Returns:
numpy.ndarray

Shape (n_modes, n_modes); the identity for an orthonormal set.

Examples

>>> import numpy as np
>>> from solprop.modes import MarcatiliMode
>>> from solprop.modes.collection import ModeCollection
>>> modes = ModeCollection(
...     [MarcatiliMode(125e-6, "He", 1.0, m=m) for m in (1, 2)]
... )
>>> bool(np.max(np.abs(modes.orthonormality() - np.eye(2))) < 1e-9)
True
property scale_invariant: bool[source]#

Return whether every mode rescales with the core radius.

Returns:
bool

True when the transform may rescale its matrices along a taper rather than rebuilding them.

classmethod slab(half_gap, orders, gas=None, pressure=0.0, *, polarisation='te', loss=True, cladding=None, temperature=293.15, susceptibility=None, mixture=None)[source]#

Build the given orders of one hollow planar guide, uniform in y.

A centred, symmetric launch couples only to odd orders, so the set a compressor run wants is usually (1, 3, 5); an offset or tilted launch needs the even ones too. Every keyword is SlabMode’s own and is given to each mode unchanged. For TE and TM rows in one set, build the two polarisations and pass them to the constructor, or use with_polarisations(): each is a distinct mode with its own row.

Parameters:
half_gapfloat or ZProfile

Half the plate separation in m, constant or along z.

orderssequence of int

Mode orders p >= 1, non-empty and without repeats, in state order.

gasGas, str or None, optional

Filling gas; None for vacuum or an explicit mixture.

pressurefloat or ZProfile, optional

Gas pressure in bar, constant or along z; default zero.

polarisation{‘te’, ‘tm’}, optional

"te" (E along y, the default) or "tm" (E along x), for every order.

lossbool, float or callable, optional

Inherited mode loss policy.

claddingGlassIndex, complex or None, optional

Plate index; None selects the default silica.

temperaturefloat, optional

Gas temperature in K.

susceptibilityGasSusceptibility or None, optional

Explicit gas susceptibility.

mixtureMixture or None, optional

Ordered species and density paths; owns the gas and susceptibility.

Returns:
ModeCollection

The validated set, in the order given.

Raises:
ModeError

If orders is empty, repeats an order, or describes an invalid mode.

Examples

>>> from solprop.modes import ModeCollection
>>> modes = ModeCollection.slab(63.5e-6, (1, 3, 5), "Ar", 1.0)
>>> [label.display_label for label in modes.channel_labels]
['TE1', 'TE3', 'TE5']
>>> modes.transverse_measure, modes.needs_full_rule
('per_unit_width', False)
property transverse_measure: Literal['area', 'per_unit_width'][source]#

Return what the set’s transverse integrals measure.

Returns:
str

"per_unit_width" for a slab set, whose powers are W/m and energies J/m, and "area" otherwise (transverse_measure()).

classmethod vincetti(radius, gas=None, pressure=0.0, count=1, *, lattice, loss=True, cladding=None, temperature=293.15, susceptibility=None, mixture=None)[source]#

Build the first count HE_1m modes of a tube-lattice fibre.

The field and transverse quadrature remain Marcatili’s. Corrections and loss are fundamental-mode fits; higher radial orders log that limitation.

Parameters:
radiusfloat or ZProfile

Positive core radius in m, constant or along z.

gasGas, str or None, optional

Filling gas; None for vacuum or an explicit mixture.

pressurefloat or ZProfile, optional

Gas pressure in bar, constant or along z; default zero.

countint, optional

Number of modes, at least one, in radial order.

latticeTubeLattice

Tube geometry shared by all modes.

lossbool, float or callable, optional

Inherited mode loss policy.

claddingGlassIndex, complex or None, optional

Tube index; None selects the default silica.

temperaturefloat or ZProfile, optional

Gas temperature in K, or a path of it along z.

susceptibilityGasSusceptibility or None, optional

Explicit gas susceptibility.

mixtureMixture or None, optional

Ordered species and density paths; owns the gas and susceptibility.

Returns:
ModeCollection

Validated modes sharing the tube geometry and cross-section.

Raises:
ModeError

If the mode count or geometry is invalid.

classmethod with_polarisations(modes)[source]#

Build a collection carrying both polarisations of every degenerate mode.

Parameters:
modessequence of Mode

The spatial modes.

Returns:
ModeCollection

The expanded set, in the fixed order of polarisation_pairs().

Examples

>>> from solprop.modes import MarcatiliMode
>>> from solprop.modes.collection import ModeCollection
>>> modes = [MarcatiliMode(125e-6, "He", 1.0, m=m) for m in (1, 2)]
>>> len(ModeCollection.with_polarisations(modes))
4
property z_constant: bool[source]#

Return whether nothing in the set varies along z.

Returns:
bool

True only when every mode says so.

solprop.modes.collection.orthonormality(modes, z=0.0)[source]#

Return the matrix of normalised overlaps of a mode set.

Parameters:
modessequence of Mode

The modes, non-empty.

zfloat, optional

Position along the waveguide, m.

Returns:
numpy.ndarray

Shape (n_modes, n_modes), symmetric, the identity for an orthonormal set.

Raises:
ModeError

If modes is empty, or the modes do not share a cross-section.

Examples

>>> import numpy as np
>>> from solprop.modes import MarcatiliMode
>>> from solprop.modes.collection import orthonormality
>>> modes = [MarcatiliMode(125e-6, "He", 1.0, m=m) for m in (1, 2)]
>>> bool(np.max(np.abs(orthonormality(modes) - np.eye(2))) < 1e-9)
True
solprop.modes.collection.overlap(first, second, z=0.0)[source]#

Return the normalised overlap integral of two modes.

Parameters:
first, secondMode

The modes, which must share a cross-section.

zfloat, optional

Position along the waveguide, m.

Returns:
float

1 for a mode with itself and 0 for an orthogonal pair. Integrated adaptively, not on the transform’s fixed rule, so that the rule can be checked against it.

Raises:
ModeError

If the modes do not share a cross-section, or either has no closed-form normalisation.

Examples

>>> from solprop.modes import MarcatiliMode
>>> from solprop.modes.collection import overlap
>>> mode = MarcatiliMode(125e-6, "He", 1.0)
>>> f"{overlap(mode, mode):.9f}"
'1.000000000'
solprop.modes.collection.polarisation_pairs(modes)[source]#

Expand a mode set so that each degenerate mode carries both polarisations.

A vector run propagates the two orthogonal polarisation states of a hybrid mode as two entries on the mode axis. The order is fixed and is the obvious one – each mode immediately followed by its partner – because the mode axis of the kernel state is positional and a reordering would be silent.

A mode with no degenerate partner, such as TE_0m or TM_0m, is carried once.

Parameters:
modessequence of Mode

The spatial modes.

Returns:
tuple of Mode

The expanded set.

Raises:
ModeError

If a mode cannot say whether it has a partner, which every ported mode can.

Examples

>>> from solprop.modes import MarcatiliMode
>>> from solprop.modes.collection import polarisation_pairs
>>> pairs = polarisation_pairs([MarcatiliMode(125e-6, "He", 1.0)])
>>> len(pairs), f"{pairs[1].phi:.4f}"
(2, '1.5708')
solprop.modes.collection.transverse_measure(modes)[source]#

Return what a mode set’s transverse integrals measure, refusing a mixture.

Parameters:
modessequence of Mode

The modes, non-empty. Each reports the optional transverse_measure trait by name; a mode without it takes the measure of the mode it was built from – a solprop.nonlinear.gnlse.SpecifiedMode made from a slab mode keeps its source’s per-unit-width normalisation and area – and measures an area when there is none.

Returns:
str

"area" or "per_unit_width" (solprop.modes.protocol.TransverseMeasure).

Raises:
ModeError

If the set is empty, or mixes a per-unit-width mode with an area mode, whose powers would be in W/m and W in one state.

Examples

>>> from solprop.modes import MarcatiliMode, SlabMode, transverse_measure
>>> transverse_measure([MarcatiliMode(125e-6)])
'area'
>>> transverse_measure([SlabMode(50e-6)])
'per_unit_width'