solprop.modes.slab#

Model the hollow planar (slab) waveguide: two plates, a gas-filled gap, uniform y.

Two parallel dielectric or metal plates at \(x = \pm a\) guide light in x and leave it free in y. This module models the guide uniform in y: the field is a plane wave in y, the mode is the one-wall-pair limit \(b \to \infty\) of the rectangular guide of solprop.modes.rectangular, and every integral over the cross-section is an integral over x per metre of y. A run on these modes reports power in W/m and energy in J/m (transverse_measure = "per_unit_width"); intensities stay W/m².

The model is Laakmann and Steier’s eq. (5) [1] with \(b \to \infty\), which is Miyagi, Hongo and Kawakami’s eq. (19) [2] and Arnold et al.’s eqs. (2)-(3) [3]:

\[\kappa_p = \frac{p\pi}{2a}\Big(1 - \frac{i\,q}{k a}\Big),\qquad n_{\rm eff}^2 = \epsilon_{\rm co} - \frac{\kappa_p^2}{k^2},\qquad q_{\rm TE} = \frac{1}{s},\quad q_{\rm TM} = \frac{\epsilon_{\rm cl}}{\epsilon_{\rm co}\, s},\]

with \(s = \sqrt{\epsilon_{\rm cl} - \epsilon_{\rm co}}\) the filled-core contrast and \(\epsilon_{\rm cl}/\epsilon_{\rm co}\) the TM-like permittivity ratio of a filled core, both shared with the rectangular guide rather than restated. To first order the field attenuation is \(\alpha_p = (1/a)(p\lambda/4a)^2\,\mathrm{Re}\,q\), so the loss goes as \(p^2\) and \(a^{-3}\), and TM/TE is \(\epsilon_{\rm cl}/\epsilon_{\rm co}\) for glass and about \(n^2 + \kappa^2\) for a metal (Miyagi eq. 36). TE has its electric field along y, parallel to the plates – the low-loss polarisation every planar compressor uses; TM has it along x, normal to them.

What a slab uniform in y cannot show. Whole-beam self-focusing, filamentation and the modulational instability in the free direction set the energy limit of real planar compressors (Arnold et al. 2009, stable to \(B \approx 15.5\)). A field uniform in y has no y structure to focus or break up, so none of that appears here. The manual page is docs/guide/waveguides/planar.md (section “Hollow planar waveguides”).

References#

[1]

K. D. Laakmann and W. H. Steier, Appl. Opt. 15, 1334-1340 (1976), doi:10.1364/AO.15.001334.

[2]

M. Miyagi, A. Hongo and S. Kawakami, IEEE J. Quantum Electron. QE-19, 136-145 (1983), doi:10.1109/JQE.1983.1071837.

[3]

C. L. Arnold et al., New J. Phys. 12, 073015 (2010), doi:10.1088/1367-2630/12/7/073015.

Module Attributes

SlabPolarisation

Name a slab family: "te" has E along y, "tm" has E along x.

Functions

gaussian_coupling(modes, waist, *[, offset, ...])

Return the share of a Gaussian beam's power each slab mode takes.

gaussian_projection(modes, waist, *[, ...])

Project a unit-peak Gaussian beam in x onto slab modes, with signs.

gaussian_to_per_unit_width(value, w_y)

Return the per-unit-width value at the centre of a Gaussian beam in y.

per_unit_width_to_gaussian(value, w_y)

Return the total of a per-unit-width quantity spread as a Gaussian in y.

Classes

PlanarGrid(modes, radius_y, n_y, *[, ...])

Describe the hybrid planar transverse grid: slab modes in x, samples in y.

SlabMode(half_gap[, gas, pressure, order, ...])

Describe one mode of a gas-filled hollow planar waveguide, uniform in y.

class solprop.modes.slab.PlanarGrid(modes, radius_y, n_y, *, window_factor=0.1, n_x=None, x_rule='gauss_kronrod')[source]#

Bases: Displayed

Describe the hybrid planar transverse grid: slab modes in x, samples in y.

A hollow planar guide resolved in both transverse directions: the field is E(x, y, t) = sum_p V_p(x) A_p(y, t), the slab modes V_p of one gap in x – sampled on the slab rule – and a periodic y axis with the Cartesian collar, exactly solprop.grid.FreeGrid’s. The state’s columns are (p, k_y): a modal amplitude per mode and raw y-DFT frequency, flattened p + n_p*iy with the mode index fastest, as a free grid has x fastest. The geometry integrates over y, so energies are in J and powers in W (transverse_measure = "area"), not per unit width as a slab uniform in y. docs/guide/waveguides/planar.md (“Resolving y”) and docs/guide/setup/spatial-decompositions.md describe it.

Attributes:
modestuple of SlabMode

The slab channels, in state order: one gap, one polarisation family, a constant gap along z. Each carries the fill.

radius_yfloat

Requested physical half-width in y, m, finite and positive.

n_yint

Samples in y, an integer of at least 2.

window_factorfloat

Added collar width divided by radius_y, in [0, 1): the periodic half-width is radius_y * (1 + window_factor); zero disables the collar. Default 0.1.

n_xint or None

Slab-rule x nodes asked of solprop.modes.quadrature.transverse_quadrature(); None (the default) takes max(32, 8 n_p, 6h + 6) with h the highest order, the modal route’s default for the same set.

x_rule{‘gauss_kronrod’, ‘gauss’}

The x rule’s family: "gauss_kronrod" (the default, as the modal route) carries its embedded Gauss rule and rounds an even n_x up by one; "gauss" has no embedded rule.

rulesolprop.modes.quadrature.TransverseQuadrature

The slab rule in x.

xnumpy.ndarray

The x nodes in m, increasing, inside the gap, shape (n_x,).

x_weightsnumpy.ndarray

Their quadrature weights in m, shape (n_x,).

y, k_ynumpy.ndarray

The centred y axis in m and its angular frequencies in rad/m in unshifted FFT order, shape (n_y,).

window_ynumpy.ndarray

The y collar, shape (n_y,).

synthesisnumpy.ndarray

Mode fields at the x nodes, each normalised to 1 W/m, in V/(m sqrt(W/m)), shape (n_x, n_p), without any temporal pad scale.

projection, projection_coarsenumpy.ndarray

The dual basis with the weights and eps0 c / 2 folded in, and its embedded coarse-rule counterpart, shape (n_p, n_x).

centrenumpy.ndarray

Each mode’s normalised field at the mid-plane x = 0, shape (n_p,).

Raises:
GridError

If the y axis, the collar or n_x is invalid.

ModeError

If the set is empty, holds a mode that is not a SlabMode, repeats a channel, mixes gaps, fills or polarisation families, or has a gap that varies along z: the hybrid transform carries a constant gap only.

Warns:
UserWarning

If n_x is below the measured 6h + 6 bound of the set’s highest order, naming the count to use.

Examples

>>> from solprop.modes import PlanarGrid, SlabMode
>>> modes = [SlabMode(63.5e-6, "Ar", 1.0, order=p) for p in (1, 3)]
>>> space = PlanarGrid(modes, 2e-3, 64)
>>> space.n_columns, space.n_nodes, space.x.size
(128, 2112, 33)
>>> space.transverse_measure
'area'
centre: Float[ndarray, 'n_p']#
property dy: float[source]#

Return the y spacing.

Returns:
float

In m.

property half_gap: float[source]#

Return the plates’ half separation.

Returns:
float

a in m.

k_y: Float[ndarray, 'n_y']#
property kperp_sq: Float[ndarray, 'n_columns'][source]#

Return each column’s k_y**2.

Returns:
numpy.ndarray

In m^-2, shape (n_columns,), the mode index fastest.

modes: Sequence[Mode]#
property n_columns: int[source]#

Return the number of state columns, (p, k_y) pairs.

Returns:
int

n_p * n_y.

property n_nodes: int[source]#

Return the number of physical nodes, (x, y) pairs with x fastest.

Returns:
int

n_x * n_y with n_x = x.size.

property n_p: int[source]#

Return the number of slab channels.

Returns:
int

len(modes).

n_x: int | None = None#
n_y: int#
property on_axis: Complex[ndarray, 'n_columns'][source]#

Return the weights that reconstruct the field at x = y = 0.

Returns:
numpy.ndarray

V_p(0) exp(-i k_y y[0]) / n_y in V/(m sqrt(W/m)), complex, shape (n_columns,): the inverse y DFT at y = 0 times each mode’s mid-plane field.

projection: Float[ndarray, 'n_p n_x']#
projection_coarse: Float[ndarray, 'n_p n_x']#
radius_y: float#
rule: TransverseQuadrature#
property spectral_weights: Float[ndarray, 'n_columns'][source]#

Return the Parseval weight of each state column.

Returns:
numpy.ndarray

dy / n_y in m, shape (n_columns,): the energy of a state of modal amplitudes in sqrt(W/m) is sum w |A|^2 integrated over time.

synthesis: Float[ndarray, 'n_x n_p']#
property transverse_measure: Literal['area', 'per_unit_width'][source]#

Return what the grid’s transverse integrals measure.

Returns:
str

"area": the grid integrates over y, so energies are J and powers W.

window_factor: float = 0.1#
window_y: Float[ndarray, 'n_y']#
x: Float[ndarray, 'n_x']#
x_rule: Literal['gauss_kronrod', 'gauss'] = 'gauss_kronrod'#
x_weights: Float[ndarray, 'n_x']#
y: Float[ndarray, 'n_y']#
class solprop.modes.slab.SlabMode(half_gap, gas=None, pressure=0.0, *, order=1, polarisation='te', loss=True, cladding=None, temperature=None, susceptibility=None, mixture=None)[source]#

Bases: _WallPairFill

Describe one mode of a gas-filled hollow planar waveguide, uniform in y.

The plates are at x = +-a and y is free. The profile is cos(p pi x / 2a) for odd p and sin(p pi x / 2a) for even p inside the gap and zero outside; the effective index is Laakmann and Steier’s one-wall-pair form with the filled-core contrast (module docstring). Everything the mode integrates is per metre of y (transverse_measure).

Attributes:
half_gapfloat or ZProfile

Half the plate separation, a, in m, positive. A path along z is a scale-invariant taper: the profile is a function of x / a(z).

gasGas or str or None

Legacy gas declaration; None means vacuum unless a mixture is supplied.

pressurefloat or ZProfile

Legacy gas pressure in bar, non-negative.

orderint

Mode order p >= 1; odd orders are symmetric about the mid-plane, so a centred launch couples only to them.

polarisation{‘te’, ‘tm’}

"te" (the default) has the electric field along y, parallel to the plates, and the low loss; "tm" has it along x, normal to them, and a loss larger by the wall’s eps_cl / eps_co for glass. The two are distinct modes with their own effective index.

lossbool or float or callable

Shared hollow-mode policy: model loss, no loss, a non-negative model scale, or an absolute power attenuation in 1/m.

claddingGlassIndex or complex or None

Plate index, positive imaginary part for absorption: a glass, a metal of solprop.physdata.metals, or a constant complex number. None selects fused silica.

temperaturefloat, ZProfile or None

Positive legacy fill temperature in K, or a path of it along z in m, which the density follows. None (the default) is 293.15 K for a gas and the mixture’s own beside a mixture; a different one beside it is refused.

susceptibilityGasSusceptibility or None

Explicit legacy gas susceptibility; None takes the built-in gas’s.

mixtureMixture or None

Ordered material populations; excludes competing legacy fill arguments.

width_xZProfile

The resolved half-gap path in m. It carries the rectangular guide’s name for its x half-width, which this is, so that the geometry-path walkers of solprop.modes.protocol.GEOMETRY_PATH_NAMES find its kinks.

densityZProfile

Total number-density path in m^-3.

cladding_indexGlassIndex or complex

Resolved plate material.

model{‘full’, ‘reduced’}

Fixed internally to full, selecting the shared evanescent floor.

References

K. D. Laakmann and W. H. Steier, Applied Optics 15, 1334-1340 (1976), doi:10.1364/AO.15.001334, eq. (5) with b -> infinity; C. L. Arnold et al., New J. Phys. 12, 073015 (2010), eqs. (2)-(3). The filled-core factor eps_cl / (eps_co s) of the TM-like coefficient follows from grazing-incidence Fresnel reflection; it reduces to the published eps_cl / s for an evacuated core.

Examples

>>> from solprop.modes import SlabMode
>>> mode = SlabMode(63.5e-6, cladding=1.45)
>>> mode.channel_label.display_label, mode.principal_component()
('TE1', 'y')
>>> f"{mode.aeff() * 1e6:.3f} um effective width"
'84.667 um effective width'
aeff(z=0.0)[source]#

Return the closed-form effective width, per unit width in y.

The mode protocol’s effective area, (int V^2)^2 / int V^4, taken over x alone because the guide is uniform in y: an effective width, in m, the same 4a/3 for every order. It is what the mode-averaged normalisation and gamma' = n2 omega / (c A_eff) read, so a mode-averaged run’s powers come out per metre of y without a second name for the same quantity.

Parameters:
zfloat, optional

Position in m.

Returns:
float

4 a / 3 in m.

at_parameters(values)[source]#

Copy the mode with the supplied gap and fill paths frozen.

Parameters:
valuescollections.abc.Mapping

A subset of the declared paths, in m, m^-3 or shared pressure in bar.

Returns:
SlabMode

A copy retaining unspecified paths and the same wall model.

Raises:
ModeError

If a path is unknown or the half-gap is invalid.

property azimuthal_order: int[source]#

Return the mode order as the rule’s resolution hint.

Returns:
int

p. A slab has no azimuthal coordinate; the x rule’s node bound is the measured solprop.modes.quadrature.cartesian_nodes() of it.

property channel_label: ChannelLabel[source]#

Return the slab family and order.

Returns:
ChannelLabel

Family TE or TM, no azimuthal order, the mode order p in the radial field – the one index the record carries – and TE1, TM3 and so on as the display label. The other polarisation is a distinct mode, not a parity partner, so parity stays None.

Examples

>>> from solprop.modes import SlabMode
>>> SlabMode(50e-6, order=3, polarisation="tm").channel_label.display_label
'TM3'
cladding: GlassIndex | complex | None = None#
cladding_index: GlassIndex | complex#
density: ZProfile#
dimlimits(z=0.0)[source]#

Return the slab cross-section and its integration bounds in x.

Parameters:
zfloat, optional

Position in m.

Returns:
tuple

("slab", (-a,), (a,)), in m: one coordinate, y being uniform.

dneff_domega(omega, z=0.0)[source]#

Return the analytic derivative including core and wall material slopes.

Parameters:
omeganumpy.ndarray

Positive frequencies in rad/s.

zfloat, optional

Position in m.

Returns:
numpy.ndarray

Complex index derivative in s/rad; only a slope-free loss callable uses the shared numerical loss derivative.

field(points, z=0.0)[source]#

Return the unnormalised real Cartesian electric-field components.

Parameters:
pointsnumpy.ndarray

(x, y) coordinates in m, shape (n_points, 2); y is ignored.

zfloat, optional

Position in m.

Returns:
numpy.ndarray

(E_x, E_y), shape (n_points, 2): the profile on E_y for TE and on E_x for TM, zero outside the gap.

Raises:
ModeError

If the points are non-finite or have an invalid shape.

gas: Gas | str | None = None#
half_gap: float | ZProfile#
loss: bool | float | Callable[[Float[ndarray, 'n_omega']], Float[ndarray, 'n_omega']] = True#
mixture: Mixture | None = None#
model: Literal['full', 'reduced'] = 'full'#
neff(omega, z=0.0)[source]#

Return the effective index with positive imaginary part for attenuation.

Parameters:
omeganumpy.ndarray

Positive frequencies in rad/s.

zfloat, optional

Position in m.

Returns:
numpy.ndarray

Complex effective index, shape (n_omega,).

neff_minus_one(omega, z=0.0)[source]#

Return the refractivity directly from the small square-root argument.

Parameters:
omeganumpy.ndarray

Positive frequencies in rad/s.

zfloat, optional

Position in m.

Returns:
numpy.ndarray

Complex n_eff - 1, without subtracting unity from the index.

normalisation(z=0.0)[source]#

Return the power per unit width carried by the unnormalised profile.

Parameters:
zfloat, optional

Position in m.

Returns:
float

eps0 c a / 2 in W/m for a unit field amplitude: the vacuum admittance times int V^2 dx = a, halved.

order: int = 1#
property orthogonal_polarisation: SlabMode[source]#

Return the same order with the other polarisation.

Returns:
SlabMode

TM for a TE mode and TE for a TM one: a distinct mode with its own effective index and its own operator row.

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

Return the gap and material paths carrying longitudinal variation.

Returns:
collections.abc.Mapping

The half-gap path plus the shared fill’s density or pressure paths.

polarisation: Literal['te', 'tm'] = 'te'#
property polarisation_degenerate: bool[source]#

Return whether the two polarisations share one effective index.

Returns:
bool

False: TE and TM take different wall coefficients, so they are distinct modes and need a row each, as the rectangular partners do.

pressure: float | ZProfile = 0.0#
principal_component(z=0.0)[source]#

Return the nonzero Cartesian electric-field component.

Parameters:
zfloat, optional

Position in m; the orientation is fixed.

Returns:
str

"y" for TE, "x" for TM.

property radius_is_constant: bool[source]#

Return the transform’s geometry-constant trait for the slab.

Returns:
bool

True when the half-gap path is constant; the historical trait name also serves Cartesian and slab modes.

property scale_invariant: bool[source]#

Return whether one scalar rescaling represents the geometry along z.

Returns:
bool

Always True: one gap is the whole geometry, and the profile is a function of x / a(z), so a taper rescales the matrices by sqrt(a(0) / a(z)) (solprop.modes.quadrature.taper_scale()).

susceptibility: GasSusceptibility | None = None#
temperature: float | ZProfile | None = None#
property transverse_measure: Literal['area', 'per_unit_width'][source]#

Return what this mode’s transverse integrals measure.

Returns:
str

"per_unit_width": the normalisation is a power per metre of y and aeff() an effective width, so a run’s powers are W/m and its energies J/m (solprop.modes.protocol.TransverseMeasure).

width_x: ZProfile#
property z_constant: bool[source]#

Return whether the gap and every material path are constant.

Returns:
bool

True if all declared paths are constant.

solprop.modes.slab.SlabPolarisation#

Name a slab family: "te" has E along y, "tm" has E along x.

alias of Literal[‘te’, ‘tm’]

solprop.modes.slab.gaussian_coupling(modes, waist, *, offset=0.0, component=None, z=0.0)[source]#

Return the share of a Gaussian beam’s power each slab mode takes.

The beam is exp(-((x - offset) / waist)^2) in x, uniform in y, with flat phase and one linear polarisation, component. It is projected onto the modes through the slab rule’s dual basis (solprop.modes.quadrature.mode_matrix()), and each share is the modal power over the beam’s whole power, inside the gap and out: what lands on the plates is lost to every mode, so the shares sum to less than one. A centred beam couples to odd orders only, and a mode polarised across the beam takes nothing.

Parameters:
modessequence of Mode

Slab modes sharing one gap, non-empty.

waistfloat

The beam’s 1/e field radius in x, m, > 0.

offsetfloat, optional

The beam centre’s displacement from the mid-plane, m.

component{‘x’, ‘y’} or None, optional

The beam’s electric-field direction: "y" launches TE, "x" TM. None (the default) takes the set’s own component, which is refused for a set carrying both, where one beam cannot be both polarisations.

zfloat, optional

Position along the guide the gap is taken at, m.

Returns:
numpy.ndarray

Fraction of the beam’s power per unit width in each mode, shape (n_modes,), in [0, 1]; zero for a mode of the other polarisation.

Raises:
ModeError

If the set is empty, is not a slab set, waist is not positive, or component is None for a set with both TE and TM modes.

Examples

>>> from solprop.modes import SlabMode, gaussian_coupling
>>> modes = [SlabMode(1.0, order=p) for p in (1, 2, 3)]
>>> [f"{share:.4f}" for share in gaussian_coupling(modes, 0.735)]
['0.9882', '0.0000', '0.0008']
>>> pair = [SlabMode(1.0), SlabMode(1.0, polarisation="tm")]
>>> [f"{share:.4f}" for share in gaussian_coupling(pair, 0.735, component="y")]
['0.9882', '0.0000']
solprop.modes.slab.gaussian_projection(modes, waist, *, offset=0.0, component=None, z=0.0)[source]#

Project a unit-peak Gaussian beam in x onto slab modes, with signs.

The beam is exp(-((x - offset) / waist)^2) in V/m, uniform in y, polarised along component. Each entry is the modal amplitude in sqrt(W/m) that beam launches into one mode: (eps0 c / 2) int E(x) e_p(x) dx over the gap, with e_p the mode’s field normalised to 1 W/m, through the slab rule’s dual basis (solprop.modes.quadrature.mode_matrix()) on a fine Gauss rule. Unlike gaussian_coupling() it keeps the sign each order takes, which a launch needs: an offset beam couples to even orders with the sign of the offset.

Parameters:
modessequence of Mode

Slab modes sharing one gap, non-empty.

waistfloat

The beam’s 1/e field radius in x, m, > 0.

offsetfloat, optional

The beam centre’s displacement from the mid-plane, m.

component{‘x’, ‘y’} or None, optional

The beam’s electric-field direction: "y" launches TE, "x" TM. None (the default) takes the set’s own component, which is refused for a set carrying both.

zfloat, optional

Position along the guide the gap is taken at, m.

Returns:
numpy.ndarray

Modal amplitudes in sqrt(W/m) per V/m of beam peak, shape (n_modes,); zero for a mode polarised across the beam.

Raises:
ModeError

If the set is empty, is not a slab set, waist is not positive, or component is None for a set with both TE and TM modes.

Examples

>>> from solprop.modes import SlabMode, gaussian_projection
>>> modes = [SlabMode(1.0, order=p) for p in (1, 2)]
>>> above = gaussian_projection(modes, 0.735, offset=0.1)
>>> below = gaussian_projection(modes, 0.735, offset=-0.1)
>>> bool(above[1] > 0), bool(below[1] < 0), bool(above[0] == below[0])
(True, True, True)
solprop.modes.slab.gaussian_to_per_unit_width(value, w_y)[source]#

Return the per-unit-width value at the centre of a Gaussian beam in y.

The inverse of per_unit_width_to_gaussian(): a total power in W or energy in J carried by exp(-(y / w_y)^2) in field, with w_y the 1/e field radius, is value / (w_y sqrt(pi / 2)) per metre of y at its centre – the number to give a run uniform in y.

Parameters:
valuefloat

Total power in W or energy in J.

w_yfloat

1/e field radius in y, m, > 0.

Returns:
float

Power in W/m or energy in J/m at the centre.

Raises:
ModeError

If w_y is not positive and finite.

Examples

>>> from solprop.modes import gaussian_to_per_unit_width
>>> f"{gaussian_to_per_unit_width(10e-3, 2e-3):.4f} J/m"
'3.9894 J/m'
solprop.modes.slab.per_unit_width_to_gaussian(value, w_y)[source]#

Return the total of a per-unit-width quantity spread as a Gaussian in y.

A run uniform in y reports power in W/m and energy in J/m: the value at the centre of a beam that is wide in y. For a beam exp(-(y / w_y)^2) in field – w_y the 1/e field radius, the 1/e^2 intensity radius – the total is value * w_y * sqrt(pi / 2), because int exp(-2 y^2 / w_y^2) dy is w_y sqrt(pi / 2). Power and energy convert alike.

Parameters:
valuefloat

Power in W/m or energy in J/m at the beam centre.

w_yfloat

1/e field radius in y, m, > 0.

Returns:
float

Total power in W or energy in J.

Raises:
ModeError

If w_y is not positive and finite.

Examples

>>> from solprop.modes import per_unit_width_to_gaussian
>>> f"{per_unit_width_to_gaussian(1.0, 2e-3):.4e}"
'2.5066e-03'