Fiber#

What the fibre is, separated from how it is simulated (decision D46).

FiberLink used to take twenty-one constructor arguments, mixing two things that change for entirely different reasons: the glass – its loss, its Kerr coefficient, its dispersion – and the numerics – how many split-steps, which step distribution, what sampling rate. A user swapping SMF for a dispersion-compensating fibre had to retype four unrelated numbers in the middle of a list of solver settings, and nothing tied them together or said where they came from.

FiberSpec is the fourth instance of the pattern D15/D43/D45 established on the frequency, delay and Raman-shift axes: a frozen value object carrying the numbers and their provenance, a registry of standard entries, and a self-check at construction against a figure published alongside them.

It also removes a latent incoherence. The link took lamb and nu as independent arguments, so a chain set to 1310 nm computed its dispersion at 1310 nm and its ASE photon energy at 1550: the two agreed only because the two defaults happened to. Here \(\nu = c / \lambda\) is derived, and cannot disagree with itself.

class FiberSpec(alpha_dB: float = 0.2, *, gamma: float = 1.3, cd_coefficient: float = 17, wavelength_nm: float = 1550, raman_gain_W_km: float | None = None, standard: str = 'custom', reference: str = '')[source]#

Physical parameters of an optical fibre, with their provenance.

Signal Model#

The three coefficients that drive the nonlinear Schrödinger equation the split-step method integrates:

\[\frac{\partial A}{\partial z} = \underbrace{-\frac{\alpha}{2} A}_{\text{loss}} \underbrace{- j \frac{\beta_2}{2} \frac{\partial^2 A}{\partial t^2}}_{\text{dispersion}} + \underbrace{j \gamma |A|^2 A}_{\text{Kerr}}\]

The dispersion is given as the engineering coefficient \(D\) in ps/nm/km, which is what data sheets quote, and converted to the group-velocity dispersion the equation uses:

\[\beta_2 = -\frac{D \lambda^2}{2 \pi c}\]

The carrier frequency is derived, \(\nu = c / \lambda\), and is not a separate parameter: two independent arguments that must agree are the defect D41 exists to prevent.

param alpha_dB:

Attenuation \(\alpha\) in dB/km.

type alpha_dB:

float, optional

param gamma:

Kerr coefficient \(\gamma\) in rad/W/km.

type gamma:

float, optional, keyword-only

param cd_coefficient:

Dispersion coefficient \(D\) in ps/nm/km, quoted at wavelength_nm. Negative for a dispersion-compensating fibre.

type cd_coefficient:

float, optional, keyword-only

param wavelength_nm:

Wavelength \(\lambda\) in nm the coefficients are quoted at, and which fixes the carrier frequency.

type wavelength_nm:

float, optional, keyword-only

param raman_gain_W_km:

Peak Raman gain \(g_R / A_\mathrm{eff}\) in \(\mathrm{W}^{-1}\mathrm{km}^{-1}\). A fibre property, not a property of the glass spectrum, which is why it lives here and not in RamanGainSpectrum. None when the entry does not quote one.

type raman_gain_W_km:

float, optional, keyword-only

param standard:

Name of the fibre, e.g. "SMF".

type standard:

str, optional, keyword-only

param reference:

Where the numbers come from.

type reference:

str, optional, keyword-only

raises ValueError:

If the attenuation is negative, if the Kerr coefficient is negative, or if the wavelength is not positive. The dispersion may be of either sign; a Raman gain may not be negative.

References

G. P. Agrawal, Nonlinear Fiber Optics, 5th ed., Academic Press, 2013, Sections 1.2 and 2.3.

Examples

>>> fiber = get_fiber("SMF")
>>> round(fiber.beta2, 3), round(fiber.carrier_frequency_Hz / 1e12, 4)
(-21.683, 193.4145)
>>> round(fiber.effective_length_km(80.0), 3)
21.169
property beta2: float#

Group-velocity dispersion \(\beta_2\) in ps²/km.

Delegates to compute_beta2(): one formula, one place. Writing the conversion a second time here got the unit scaling wrong by a factor of a thousand, and the self-check below is what caught it.

property carrier_frequency_Hz: float#

Optical carrier \(\nu = c / \lambda\), in Hz.

property alpha_per_km: float#

Attenuation \(\alpha\) in 1/km, the equation’s unit.

effective_length_km(length_km: float) → float[source]#

Nonlinear effective length \((1 - e^{-\alpha L})/\alpha\).

The length a lossless fibre would need to accumulate the same Kerr phase. Equal to length_km for a lossless fibre.

Parameters:

length_km (float) – Physical span length \(L\) in km.

Returns:

float – Effective length in km.

Examples

>>> round(get_fiber("SMF").effective_length_km(80.0), 3)
21.169
>>> round(FiberSpec(0.0).effective_length_km(80.0), 3)
80.0
loss_dB(length_km: float) → float[source]#

Total attenuation of a span of length_km, in dB.

get_fiber(standard: str, **kwargs: Any) → FiberSpec[source]#

Return a catalog fibre by name.

Parameters:
  • standard (str) – Entry name; see available_fibers().

  • **kwargs – Forwarded to the entry factory.

Raises:

KeyError – If the name is not in the catalog.

Examples

>>> get_fiber("SMF").cd_coefficient
17.0
register_fiber(name: str)[source]#

Register a catalog entry; users can add their own fibres.

Parameters:

name (str) – Name the factory answers to in get_fiber().

Examples

>>> @register_fiber("LAB")
... def _lab():
...     return FiberSpec(0.19, gamma=1.2, standard="LAB",
...                      reference="bench")
>>> get_fiber("LAB").alpha_dB
0.19
available_fibers() → list[str][source]#

Names accepted by get_fiber().

Returns:

list of str – Registered names, sorted.

Examples

>>> available_fibers()
['DCF', 'NZDSF', 'SMF']