solprop.nonlinear.gnlse#
The GNLSE as a mode-averaged envelope problem.
The generalised nonlinear Schroedinger equation is not a separate solver path in
solprop: the field is primary and the GNLSE is an interface mapping. It is the
mode-averaged envelope problem with a specified mode
(SpecifiedMode) — a mode whose propagation constant is given directly, as a
Taylor series, a table, or a declared path of either along z, rather than derived from
a waveguide geometry — plus a mapping from the usual GNLSE coefficient gamma onto
the third-order susceptibility the Kerr response takes. The shock term is not a
separate operator either: it is the omega dependence of the normalisation
prefactor (solprop.nonlinear.normalisation).
When Raman is off and no fraction is given the whole of gamma acts as Kerr, so
an “N = 6” soliton is N = 6. A fraction given explicitly is always held back from the
Kerr term, with Raman on or off, so that switching Raman alone adds the delayed response
without changing the instantaneous one.
The derivation and a worked example are in docs/guide/waveguides/gnlse.md.
Module Attributes
A propagation constant, or a derivative of one, as a function of frequency. |
|
A power attenuation coefficient in 1/m as a function of angular frequency. |
Functions
|
Coerce a frequency-only dispersion to a path, and pass a path through. |
|
Convert a power loss in dB/m to a power attenuation coefficient in 1/m. |
|
Return the Kerr susceptibility of the GNLSE mapping. |
|
Map GNLSE parameters onto a mode-averaged envelope problem. |
Return the Raman strength of the GNLSE mapping, as an integrated response. |
Classes
|
Adapt a frequency-only dispersion or loss to the path interface. |
|
A spectral quantity that follows a declared path along |
|
A GNLSE fibre expressed as a mode-averaged envelope problem. |
|
|
|
A mode defined by its propagation constant rather than by a waveguide. |
|
|
|
|
- class solprop.nonlinear.gnlse.ConstantDispersion(function)[source]#
Bases:
objectAdapt a frequency-only dispersion or loss to the path interface.
The counterpart of
solprop.profiles.Constantfor a spectral quantity: it ignoresz, declares no breakpoints and reports itself constant, so a fibre whose dispersion does not move stays on the exact constant-operator path and its arithmetic is unchanged – the wrapped function is called with exactly the argument it was called with before.- Attributes:
- functioncallable
f(omega)returning an array of the same shape.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import ConstantDispersion, TaylorDispersion >>> path = ConstantDispersion(TaylorDispersion([1.0e7], 2.0e15)) >>> path.is_constant, path.breakpoints.size (True, 0) >>> float(path(np.array([2.0e15]), 3.0)[0]) 10000000.0
- property breakpoints: Float[ndarray, 'n_breakpoints'][source]#
Return the empty set of breakpoints.
- Returns:
- numpy.ndarray
An empty float array: a constant path kinks nowhere.
- solprop.nonlinear.gnlse.DispersionFunction#
A propagation constant, or a derivative of one, as a function of frequency.
alias of
Callable[[Float[ndarray, 'n_omega']],Float[ndarray, 'n_omega']]
- class solprop.nonlinear.gnlse.DispersionPath(*args, **kwargs)[source]#
Bases:
ProtocolA spectral quantity that follows a declared path along
z.The spectral sibling of
solprop.profiles.ZProfile: where aZProfilecarries one scalar along the fibre, this carries a whole frequency axis –beta(omega, z),d beta / d omega (omega, z)oralpha(omega, z). The three members below are the ones the translation layer reads: the breakpoints become the hard kinks the schedule lands on, the span is what a run is checked against, and is_constant is what keeps a fibre that does not move on the exact constant-operator path.A mode built on one is z-varying by construction, and
solprop.translate.linop.build_phi()resolves it along z exactly as it resolves a gas gradient: the operator is sampled at positions, the declared breakpoints are mandatory interval edges, and the Chebyshev integral is exact in between (docs/guide/numerics/linear-operator.md).Implementations here are
ConstantDispersion, which adapts aDispersionFunctionof frequency alone, andInterpolatedDispersion, which interpolates declared sets. Anything with these four members is accepted: the protocol is runtime-checkable andas_dispersion_path()is the one coercion.
- class solprop.nonlinear.gnlse.GnlseProblem(mode, kerr, omega0, gamma, n2, chi3, aeff, raman_fraction, shock, prefactor_index=1.0, raman=None, bundle=None, fraction_origin='', n2_origin='user')[source]#
Bases:
objectA GNLSE fibre expressed as a mode-averaged envelope problem.
Everything the translation layer needs to build a kernel problem for the GNLSE, and nothing else: the mode carries the dispersion and the loss, the response carries the nonlinearity, and the two scalars say how the shock term and any future Raman response are configured. The coefficients that went into the mapping are kept so that a run records what it was asked for.
- Attributes:
- modesolprop.modes.protocol.Mode
The fibre:
beta(omega), the loss and the effective area.- kerrsolprop.nonlinear.responses.KerrResponse
The Kerr response, with
thg=False: an envelope GNLSE grid carries no third harmonic.- ramansolprop.nonlinear.raman.RamanResponse or None
The delayed response taking
raman_fractionof the nonlinearity, orNonewhen Raman is off: the optmat silica bundle’s sampled shape, normalised toraman_susceptibility()of n2 and scaled by its own fraction.- bundlesolprop.physdata.glasses.SilicaBundle or None
The optmat silica bundle the fraction, the shape or
n2came from;Nonewhen none was used. Recorded in the run’s material manifest, not in its configuration.- fraction_originstr
Where
raman_fractioncame from:"user"(given),"bundle"(the bundle’sf_R, Raman on) or"raman-off"(0, Raman off and none given).- n2_originstr
"user"for a givengammaorn2,"bundle"forn2="recommended", the bundle’s totaln2at the carrier.- omega0float
Reference angular frequency the mapping used, rad/s.
- gammafloat
Nonlinear coefficient in 1/(W m),
n2 omega0 / (c A_eff).- n2float
Nonlinear index in m^2/W.
- chi3float
Third-order susceptibility in m^2/V^2, already reduced by the Raman fraction.
- aefffloat
Effective area in m^2.
- raman_fractionfloat
Share of
gammaheld back from the Kerr term, between 0 and 1. The Raman response carries it when there is one; with Raman off it is zero unless a fraction was given, and then that share ofgammais not propagated.- shockbool
Whether the normalisation prefactor keeps its
omegadependence.- prefactor_indexfloat
Effective index the normalisation prefactor uses, always 1.0. The GNLSE’s
betais a fitted dispersion rather than a physical effective index, soc beta / omeganeed not be near one and must not be used here. Pass it tosolprop.nonlinear.normalisation.uppe_prefactor()asindex.
Examples
>>> from solprop.nonlinear.gnlse import problem_from >>> problem = problem_from([0.0, 5.0e-9, -2.0e-26], omega_ref=1.8e15, gamma=0.1) >>> f"{problem.gamma:.6f}", problem.raman_fraction, problem.kerr.thg ('0.100000', 0.0, False) >>> problem.responses (KerrResponse(chi3=..., thg=False, species=None),)
- bundle: SilicaBundle | None = None#
- kerr: KerrResponse#
- raman: RamanResponse | None = None#
- class solprop.nonlinear.gnlse.InterpolatedDispersion(positions, members)[source]#
Bases:
objectbeta(omega, z)from declared sets at declared positions, linear in z.The one mechanism a z-varying specified fibre runs through. Each member is an ordinary frequency-only dispersion – a
TaylorDispersionexpansion set, aTabulatedDispersioncolumn of ann_eff(omega, z)table, or any callable – and between two positions the value is the straight linebeta(omega, z) = (1 - t) beta_i(omega) + t beta_{i+1}(omega),t = (z - z_i) / (z_{i+1} - z_i),which is exact at every declared position and reproduces the members there bit for bit. Outside the declared range the value is held at the nearest end, as a
solprop.profiles.PiecewiseLinearscalar path is: that covers the few per cent of margin the cumulative operator tabulates beyondz_max, while a run longer than the declared range is refused by name bysolprop.translate.linop.build_phi()rather than silently extrapolated.A kink is a declared position and nothing else, which is why the positions are the
breakpoints: the linear interpolant is smooth in between, so the adaptive Chebyshev split resolves each section at degree one and the integral of the operator over it is exact.Equality is by identity (
eq=False) because the positions are an array.- Attributes:
- positionsnumpy.ndarray
Strictly increasing positions in m, shape
(n_positions,), at least two and all finite. Adopted as a read-only float64 array, so that two runs specified the same way record the same positions and serialise alike.- memberstuple of callable
One dispersion per position, in the same order. A sequence is adopted as a tuple.
- Raises:
- ModeError
If fewer than two positions are given, they are not strictly increasing or finite, or the number of members does not match.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import InterpolatedDispersion, TaylorDispersion >>> entrance = TaylorDispersion([0.0, 5.0e-9], 2.0e15) >>> exit_set = TaylorDispersion([0.0, 7.0e-9], 2.0e15) >>> path = InterpolatedDispersion([0.0, 1.0], (entrance, exit_set)) >>> float(path(np.array([3.0e15]), 0.5)[0]) 6000000.0
- property breakpoints: Float[ndarray, 'n_breakpoints'][source]#
Return the declared positions, every one of which is a kink.
- Returns:
- numpy.ndarray
The positions in m, shape
(n_positions,).
- property is_constant: bool[source]#
Return whether every position declares the same set.
- Returns:
- bool
Trueonly when one object was declared at every position, which is the conservative test an arbitrary callable admits.
- solprop.nonlinear.gnlse.LossFunction#
A power attenuation coefficient in 1/m as a function of angular frequency.
alias of
Callable[[Float[ndarray, 'n_omega']],Float[ndarray, 'n_omega']]
- class solprop.nonlinear.gnlse.SpecifiedMode(dispersion, dispersion_derivative, loss, effective_area)[source]#
Bases:
objectA mode defined by its propagation constant rather than by a waveguide.
This is the GNLSE’s “fibre”:
beta(omega)given as data, a power loss, and an effective area. It implementssolprop.modes.protocol.Modein full, so the linear operator, the normalisation and the mode-averaged transform treat it exactly like a capillary mode and the GNLSE needs no special path (seedocs/dev/decisions.md).Build one with
from_taylor()orfrom_table()for a fibre that does not move, and withfrom_taylor_path(),from_index_path()orfrom_mode()for one that does; the constructor takes the resolved functions and is the escape hatch for an arbitrary analytic dispersion.Two paths along z, declared together. The dispersion, its derivative and the loss are each either a function of frequency alone or a
DispersionPath; the effective area is either a number or asolprop.profiles.ZProfile. A fibre whose dispersion moves must declare an area that moves with it, at the same declared positions – no real waveguide changes one without the other – and the area-only case, a taper whose dispersion is held fixed, stays as the documented constant-dispersion special case. The z dependence then goes through the one mechanism a gas gradient goes through: the declared positions are the hard kinks, the cumulative operator resolvesD(omega, z)between them and integrates it exactly (docs/guide/numerics/linear-operator.md).The transverse profile is a uniform disc of area
effective_area, which is the profile whose effective area is exactly that area, so the single-node mode-averaged transform is well defined and agrees withaeff().- Attributes:
- dispersioncallable or DispersionPath
beta(omega)in 1/m, taking and returning an array of shape(n_omega,), or a declared pathbeta(omega, z).- dispersion_derivativecallable or DispersionPath
d beta / d omegain s/m, analytic, same shapes; a path whendispersionis one, declared at the same positions.- lossfloat or callable or DispersionPath
Power attenuation in 1/m: a constant, a function of
omegawith the same shapes, or a declared path. Convert a figure in dB/m withattenuation_from_db().- effective_areafloat or ZProfile
A_effin m^2, positive: a constant, or a declared path along the fibre. A path makes the mode z-varying, and the area then enters exactly once, as the shape of the nonlinear coefficient: the mode-averaged transform tabulatessqrt(A_eff(0) / A_eff(z))and the response prefactor keeps the entrance area, sogamma(z) = gamma A_eff(0) / A_eff(z)with the suppliedgammaas the entrance coefficient.
- Raises:
- ModeError
If the effective area is not positive and finite anywhere it is declared, if the dispersion moves without the area, if the value and the derivative disagree about whether they move, or if the dispersion and the area are declared at different positions.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> mode = SpecifiedMode.from_taylor( ... [1.0e7, 5.0e-9, -2.0e-26], omega_ref=2.0e15, aeff=1e-10 ... ) >>> f"{float(mode.beta(np.array([2.0e15]))[0]):.6e}" '1.000000e+07' >>> mode.aeff() 1e-10
- aeff(z=0.0)[source]#
Return the effective area at a position.
- Parameters:
- zfloat, optional
Position along the waveguide, m; it is the declared area path that is evaluated, and a constant area ignores it.
- Returns:
- float
A_eff(z)in m^2.
- alpha(omega, z=0.0)[source]#
Return the power attenuation coefficient.
- Parameters:
- omeganumpy.ndarray
Angular frequencies, rad/s, shape
(n_omega,).- zfloat, optional
Position along the waveguide, m. It reaches the declared loss path; a constant or a function of frequency alone ignores it.
- Returns:
- numpy.ndarray
alpha(omega, z)in 1/m, shape(n_omega,), so that the power falls asexp(-alpha z).
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import ( ... SpecifiedMode, ... attenuation_from_db, ... ) >>> mode = SpecifiedMode.from_taylor( ... [1.0e7], omega_ref=2.0e15, loss=attenuation_from_db(10.0) ... ) >>> f"{mode.alpha(np.array([2.0e15]))[0]:.6f}" '2.302585'
- property area_path: ZProfile[source]#
Return the declared effective-area path in m^2.
- Returns:
- solprop.profiles.ZProfile
The path, a
Constantwhen the area is a number. It is the path the transform’s kink walk andsolprop.translate.linop.declared_paths()take their breakpoints from, so that the tables split there and the schedule lands on them.
- at_parameters(values)[source]#
Return the same fibre with its area frozen at a value.
- Parameters:
- valuescollections.abc.Mapping
Any subset of
{"effective_area"}, in m^2 and positive. A name left out keeps its path; a name this mode does not declare is an error.
- Returns:
- SpecifiedMode
The same dispersion and loss with a constant effective area.
- Raises:
- ModeError
If a name is not declared, the value is not a positive area, or the dispersion follows a path along z – there is then no parameter value that reproduces this mode, which is what
parameters_own_zsays in advance.
- property azimuthal_order: int[source]#
Highest azimuthal order of the profile.
- Returns:
- int
Always 0: the disc is azimuthally uniform.
- beta(omega, z=0.0)[source]#
Return the propagation constant.
Unlike
neff()this is defined at zero frequency, so it is the right entry point for anything that works over a whole field grid.- Parameters:
- omeganumpy.ndarray
Angular frequencies, rad/s, shape
(n_omega,).- zfloat, optional
Position along the waveguide, m. It reaches the declared dispersion path; a dispersion given as a function of frequency alone ignores it.
- Returns:
- numpy.ndarray
beta(omega, z)in 1/m, shape(n_omega,).
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> mode = SpecifiedMode.from_taylor([1.0e7], omega_ref=2.0e15) >>> mode.beta(np.array([0.0, 3.0e15])).tolist() [10000000.0, 10000000.0]
- property channel_label: ChannelLabel[source]#
Name this channel: a dispersion, not an eigenmode of a guide.
- Returns:
- ChannelLabel
Family
specified, with no azimuthal or radial order, no parity and no handedness: a mode given bybeta(omega)has no quantum numbers.
Examples
>>> from solprop.nonlinear.gnlse import SpecifiedMode >>> mode = SpecifiedMode.from_taylor([1.0e7], omega_ref=2.0e15) >>> mode.channel_label.display_label 'specified'
- property derivative_path: DispersionPath[source]#
Return
d beta / d omegaas a path along z.- Returns:
- DispersionPath
The declared path, a
ConstantDispersionwhen the derivative is a function of frequency alone.
- dimlimits(z=0.0)[source]#
Return the cross-section parametrisation and the integration limits.
- Parameters:
- zfloat, optional
Position along the waveguide, m; the surrogate disc follows the area.
- Returns:
- tuple
("polar", (0.0, 0.0), (radius(z), 2 pi))in m and rad.
- dispersion: Callable[[Float[ndarray, 'n_omega']], Float[ndarray, 'n_omega']] | DispersionPath#
- dispersion_derivative: Callable[[Float[ndarray, 'n_omega']], Float[ndarray, 'n_omega']] | DispersionPath#
- property dispersion_is_constant: bool[source]#
Whether the spectral quantities are functions of frequency alone.
- Returns:
- bool
Truewhen the dispersion, its derivative and the loss all report themselves constant along z.
Examples
>>> from solprop.nonlinear.gnlse import SpecifiedMode >>> SpecifiedMode.from_taylor([1.0e7], omega_ref=2.0e15).dispersion_is_constant True
- property dispersion_path: DispersionPath[source]#
Return the propagation constant as a path along z.
- Returns:
- DispersionPath
The declared path, a
ConstantDispersionwhen the dispersion is a function of frequency alone. Its breakpoints are kinks of the cumulative operator exactly as the area path’s are.
- dneff_domega(omega, z=0.0)[source]#
Return the analytic frequency derivative of the effective index.
Differentiating
n_eff = c (beta + i alpha / 2) / omegagives(c beta' - n_eff) / omegaplusi c alpha' / (2 omega). The second term is zero for a constant loss and is dropped for a callable one, which is the only approximation in this class: the co-moving frame quantities take the real part, where the loss never appears, sobeta_0andbeta_1are unaffected either way.- Parameters:
- omeganumpy.ndarray
Angular frequencies, rad/s, shape
(n_omega,), strictly positive.- zfloat, optional
Position along the waveguide, m. It reaches the declared dispersion and loss paths; quantities given as functions of frequency alone ignore it.
- Returns:
- numpy.ndarray
d n_eff / d omegain s/rad, complex, shape(n_omega,).
- Raises:
- ModeError
If any frequency is not strictly positive.
Examples
>>> import numpy as np >>> from solprop.constants import C >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> flat = SpecifiedMode.from_taylor( ... [1.5 * 2.0e15 / C, 1.5 / C], omega_ref=2.0e15 ... ) >>> bool(np.allclose(flat.dneff_domega(np.array([2.0e15])), 0.0, atol=1e-28)) True
- field(points, z=0.0)[source]#
Return the transverse field components at points of the cross-section.
A uniform disc polarised along x: one inside the radius, zero outside. Its effective area
(int |e|^2 dA)^2 / int |e|^4 dAis the disc’s area, which isaeff()by construction, so the single-node mode-averaged transform is consistent with the normalisation.- Parameters:
- pointsnumpy.ndarray
(r, theta)in m and rad, shape(n_points, 2).- zfloat, optional
Position along the waveguide, m; the disc follows the declared area.
- Returns:
- numpy.ndarray
(E_x, E_y)of the unnormalised profile, shape(n_points, 2), real.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> mode = SpecifiedMode.from_taylor([1.0], omega_ref=1.0, aeff=np.pi) >>> points = np.array([[0.5, 0.0], [1.5, 0.0]]) >>> mode.field(points).tolist() [[1.0, 0.0], [0.0, 0.0]]
- classmethod from_index_path(omega, neff, positions, *, aeff)[source]#
Build a fibre from an
n_eff(omega, z)table and its area path.The table’s real part is the propagation constant
beta = omega Re n_eff / cand its imaginary part the power attenuationalpha = 2 omega Im n_eff / c(docs/dev/conventions.mdsection 2,Im n_eff > 0is loss). Each row is one position’s spectrum, a cubic spline in frequency differentiated exactly in the spline basis, and the rows are interpolated linearly inz.The frequency axis is shared by every position, and it must span the run’s active band: a spline evaluated outside the table it was built from is refused by name rather than extrapolated, which is the classic way to get a plausible, wrong answer.
- Parameters:
- omeganumpy.ndarray
Strictly increasing angular frequencies, rad/s, shape
(n_nodes,), at leastsolprop.modes.tabulated.MIN_TABLE_POINTSof them.- neffnumpy.ndarray
Complex effective index at each position and frequency, shape
(n_positions, n_nodes).- positionsarray_like
Strictly increasing positions in m, shape
(n_positions,), at least two.- aeffarray_like or ZProfile
Effective area in m^2: one positive value per position, or a declared path whose breakpoints are exactly positions.
- Returns:
- SpecifiedMode
The mode.
- Raises:
- ModeError
If the table’s shape does not match the axes, fewer than two positions are given, or the two paths are declared at different positions.
Examples
>>> import numpy as np >>> from solprop.constants import C >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> omega = np.linspace(1.0e15, 3.0e15, 8) >>> index = np.stack([np.full(8, 1.45 + 0j), np.full(8, 1.46 + 0j)]) >>> mode = SpecifiedMode.from_index_path( ... omega, index, [0.0, 1.0], aeff=[1e-10, 2e-10] ... ) >>> f"{float(mode.neff(np.array([2.0e15]), 0.5).real[0]):.6f}" '1.455000'
- classmethod from_index_table(table, *, aeff=1.0)[source]#
Build a dispersion-specified mode from a complex-index table.
- Parameters:
- tablesolprop.modes.tabulated.IndexTable
Whole effective index and loss, with finite spectral support.
- aefffloat or ZProfile, optional
Positive propagation area in m^2, or a declared path along the fibre. The driver selects the table’s area at its reference wavelength; default 1.0 is the gamma convention.
- Returns:
- SpecifiedMode
Mode with analytic real-index derivative and reproducible callables.
- Raises:
- ModeError
If the propagation area is not positive.
- classmethod from_mode(mode, *, aeff=None, wavelength=None)[source]#
Use a mode’s dispersion, loss and area for a GNLSE fibre.
Its fill contributes to the supplied dispersion and loss, but is ignored as a nonlinear material. The GNLSE caller supplies gamma or n2 instead. Frozen callables retain the original mode for reproducible records.
A source whose geometry moves hands over both paths. The frozen callables forward the position to the source mode, so its dispersion, its derivative and its loss are evaluated where they are asked for rather than frozen at the entrance, and the effective area is sampled from the same mode over the same declared positions – the two come together, so a moving source is accepted rather than refused. A source that does not move keeps the ordinary constant path, bit for bit.
A provider channel’s area belongs to a frequency. A
ProviderChannelMode’s own area is the surrogate at the median knot of the band its guide was sampled on, which is not in general the pump: on worked example 05’s graded-index taper it is 35 % above the pump’s. With wavelength, the area is solved again at that wavelength instead (area_at()) – once for a snapshot, and at every sampled position of a moving guide’s path, so that a moving channel hands over an area path at the pump. With neither aeff nor wavelength, the median-knot surrogate is used and aSurrogateAreaWarningsays so.solprop.driver.interface.prop_gnlse()passes itslambda0.- Parameters:
- modesolprop.modes.protocol.Mode
Source mode.
- aefffloat or ZProfile or None, optional
Positive finite effective area in m^2, or a declared path.
Nonetakes the source’s declared area path if it has one, samples the source’s ownaeffalong z when its geometry moves, and otherwisemode.aeff()– or, for a provider channel given wavelength, the channel’s area solved at that wavelength.- wavelengthfloat or None, optional
Positive wavelength in m the area of a provider channel is taken at, normally the pump’s; read only when aeff is
None. Every other mode’s area is not a function of frequency in theModeprotocol – it is declared once, asTabulatedMode’s is at its ownreference_wavelength– so there it is accepted and ignored, which is what letssolprop.driver.interface.prop_gnlse()pass itslambda0for any fibre.
- Returns:
- SpecifiedMode
GNLSE mode with an analytic first dispersion derivative.
- Raises:
- ModeError
If the source’s geometry moves and a constant area is asked for, if a moving source declares no path saying where it moves, if the area is not positive and finite, if wavelength is not positive and finite, or if a provider channel asked for its area at wavelength carries no provider to solve.
- Warns:
- SurrogateAreaWarning
For a provider channel given neither aeff nor wavelength.
- classmethod from_table(omega, beta, *, loss=0.0, aeff=1.0)[source]#
Build a mode from a tabulated propagation constant.
The table is interpolated by a cubic spline and differentiated exactly in the spline basis, so
dneff_domegastays analytic, as the co-moving frame requires. Evaluation outside the tabulated range raises rather than extrapolating.- Parameters:
- omeganumpy.ndarray
Strictly increasing angular frequencies, rad/s, shape
(n_nodes,), at least four.- betanumpy.ndarray
Propagation constant at those frequencies, 1/m, shape
(n_nodes,).- lossfloat or callable, optional
Power attenuation in 1/m; a constant or a function of
omega. Default 0.- aefffloat or ZProfile, optional
Effective area in m^2, or a declared path along the fibre. Default 1.0.
- Returns:
- SpecifiedMode
The mode.
- Raises:
- ModeError
If the two arrays disagree in shape, the table is shorter than four points, the frequencies are not strictly increasing, or
aeffis not positive.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> omega = np.linspace(1.0e15, 3.0e15, 8) >>> mode = SpecifiedMode.from_table(omega, 5.0e-9 * omega) >>> f"{float(mode.beta(np.array([2.0e15]))[0]):.6f}" '10000000.000000'
- classmethod from_taylor(betas, omega_ref, *, loss=0.0, aeff=1.0)[source]#
Build a mode from Taylor coefficients of
betaabout a frequency.beta(omega) = sum_n betas[n] (omega - omega_ref)**n / n!. There is no constructor that centres the series at zero frequency, because a high-order Taylor polynomial extrapolated from DC to the optical band is meaningless.- Parameters:
- betasarray_like
Taylor coefficients
beta_0, beta_1, ...in s^n/m, at least one.- omega_reffloat
Frequency the series is expanded about, rad/s, positive.
- lossfloat or callable, optional
Power attenuation in 1/m; a constant or a function of
omega. Default 0.- aefffloat or ZProfile, optional
Effective area in m^2, or a declared path along the fibre. Default 1.0, which is the GNLSE convention in which the area has been folded into
n2already.
- Returns:
- SpecifiedMode
The mode.
- Raises:
- ModeError
If no coefficients are given,
omega_refis not positive, oraeffis not positive.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> mode = SpecifiedMode.from_taylor([0.0, 5.0e-9], omega_ref=2.0e15) >>> f"{float(mode.beta(np.array([3.0e15]))[0]):.6f}" '5000000.000000'
- classmethod from_taylor_path(betas, positions, omega_ref, *, aeff, loss=0.0)[source]#
Build a fibre whose Taylor dispersion and area are declared along z.
One expansion set and one effective area per declared position, both interpolated linearly in between: the two are sampled together, which is what makes the model consistent. Between two positions the propagation constant is the straight line between the two sets, so the cumulative operator resolves each section at degree one in
zand integrates it exactly, and the declared positions are hard kinks the schedule lands on.- Parameters:
- betasarray_like
Taylor coefficients
beta_0, beta_1, ...in s^n/m at each position, shape(n_positions, n_deg). Every row carries the same number of coefficients.- positionsarray_like
Strictly increasing positions in m, shape
(n_positions,), at least two. They must cover the run, whichsolprop.translate.linop.build_phi()checks by name.- omega_reffloat
Frequency every series is expanded about, rad/s, positive.
- aeffarray_like or ZProfile
Effective area in m^2: one positive value per position, or a declared path whose breakpoints are exactly positions.
- lossfloat or callable or DispersionPath, optional
Power attenuation in 1/m. Default 0.
- Returns:
- SpecifiedMode
The mode, with an analytic first dispersion derivative at every position.
- Raises:
- ModeError
If the rows and the positions disagree, fewer than two positions are given, the areas are not positive, or the two paths are declared at different positions.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> mode = SpecifiedMode.from_taylor_path( ... [[0.0, 5.0e-9], [0.0, 7.0e-9]], ... [0.0, 1.0], ... omega_ref=2.0e15, ... aeff=[1e-10, 2e-10], ... ) >>> f"{float(mode.beta(np.array([3.0e15]), 0.5)[0]):.6f}" '6000000.000000' >>> mode.z_constant False
- loss: float | Callable[[Float[ndarray, 'n_omega']], Float[ndarray, 'n_omega']] | DispersionPath#
- property loss_path: DispersionPath[source]#
Return the power attenuation as a path along z.
- Returns:
- DispersionPath
The declared path; a constant number becomes a uniform attenuation at every frequency and every position.
- neff(omega, z=0.0)[source]#
Return the complex effective index.
n_eff = c (beta + i alpha / 2) / omega, the inverse of thebetaandalphadefinitions indocs/dev/conventions.mdsection 2, so thatIm n_eff > 0is loss.- Parameters:
- omeganumpy.ndarray
Angular frequencies, rad/s, shape
(n_omega,), strictly positive.- zfloat, optional
Position along the waveguide, m. It reaches the declared dispersion and loss paths; quantities given as functions of frequency alone ignore it.
- Returns:
- numpy.ndarray
n_eff(omega), complex, shape(n_omega,).
- Raises:
- ModeError
If any frequency is not strictly positive. A specified mode has no effective index at zero frequency. Evaluate on the grid’s active band instead.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> mode = SpecifiedMode.from_taylor( ... [1.0e7, 5.0e-9], omega_ref=2.0e15 ... ) >>> f"{mode.neff(np.array([2.0e15]))[0].real:.6f}" '1.498962'
- neff_minus_one(omega, z=0.0)[source]#
Return
n_eff - 1formed without cancellation.(c (beta + i alpha / 2) - omega) / omega: the subtraction happens on quantities of the same size rather than after a division that lands near one.- Parameters:
- omeganumpy.ndarray
Angular frequencies, rad/s, shape
(n_omega,), strictly positive.- zfloat, optional
Position along the waveguide, m. It reaches the declared dispersion and loss paths; quantities given as functions of frequency alone ignore it.
- Returns:
- numpy.ndarray
n_eff - 1, complex, shape(n_omega,).
- Raises:
- ModeError
If any frequency is not strictly positive.
Examples
>>> import numpy as np >>> from solprop.constants import C >>> from solprop.nonlinear.gnlse import SpecifiedMode >>> mode = SpecifiedMode.from_taylor( ... [1.45 * 2.0e15 / C], omega_ref=2.0e15 ... ) >>> f"{mode.neff_minus_one(np.array([2.0e15]))[0].real:.6f}" '0.450000'
- property parameter_paths: Mapping[str, ZProfile][source]#
Return the scalar path this mode’s z dependence runs through.
- Returns:
- collections.abc.Mapping
{"effective_area": path}in m^2.
Notes
When the dispersion is a function of frequency alone – the documented special case, a taper whose dispersion is held fixed – the declared area is the whole of the z dependence and
solprop.modes.protocol.ParameterisedModeholds exactly: the linear operator is expanded in a parameter it does not depend on, at degree zero, and the path’s breakpoints become the hard kinks the schedule lands on.When the dispersion follows a path the area is no longer the whole of it, and
at_parameters()cannot return a z-constant mode. The declaration is still the mode’s kinks, which is whatsolprop.translate.linop.declared_paths()reads it for; the expansion is not taken, becausesolprop.translate.linop.parameter_paths()checks that freezing a mode’s parameters leaves it z-constant and falls back to the z table when it does not.Examples
>>> from solprop.nonlinear.gnlse import SpecifiedMode >>> from solprop.profiles import Linear >>> mode = SpecifiedMode.from_taylor( ... [0.0], omega_ref=2.0e15, aeff=Linear(1e-10, 2e-10, 1.0) ... ) >>> sorted(mode.parameter_paths) ['effective_area']
- property parameters_own_z: bool[source]#
Whether the declared parameters are the whole of this mode’s z dependence.
The trait the translation layer reads before it expands the cumulative operator in a mode’s parameters instead of tabulating it along z. It is
Truefor the constant-dispersion special case, where the declared area is everything, andFalseonce the dispersion follows a path, which no scalar parameter can be frozen at.- Returns:
Examples
>>> from solprop.nonlinear.gnlse import SpecifiedMode >>> SpecifiedMode.from_taylor([0.0], omega_ref=2.0e15).parameters_own_z True
- principal_component(z=0.0)[source]#
Return the polarisation of the uniform surrogate profile.
- Parameters:
- zfloat, optional
Position along the fibre, m; ignored by the uniform profile.
- Returns:
- {‘x’}
The sole nonzero Cartesian component.
- radius(z=0.0)[source]#
Return the radius of the equivalent uniform disc.
- Parameters:
- zfloat, optional
Position along the waveguide, m.
- Returns:
- float
sqrt(A_eff(z) / pi)in m, the radius of the disc whose effective area isA_eff(z).
- class solprop.nonlinear.gnlse.TabulatedDispersion(omega, beta, derivative=0)[source]#
Bases:
objectbeta(omega), or a derivative of it, from a table, by cubic spline.A cubic spline through the table, differentiated exactly in the spline basis, as the co-moving frame requires. Extrapolation is refused rather than silently produced: a spline dispersion evaluated outside the data it was built from is the classic way to get a plausible, wrong answer.
Callable and frozen for the reason
TaylorDispersiongives: a mode built from it records its table, not a closure’s memory address, so a run can be resumed from its own file in another process.Equality is by identity (
eq=False) because the table is an array.- Attributes:
- omeganumpy.ndarray
Strictly increasing angular frequencies, rad/s, shape
(n_nodes,), at leastsolprop.modes.tabulated.MIN_TABLE_POINTSof them.- betanumpy.ndarray
Propagation constant at those frequencies, 1/m, the same shape. The two arrays carry independent axis names on purpose: their agreement is what
__post_init__checks, and that check must stay reachable in production, where the test session’s runtime type checker is absent.- derivativeint
How many times to differentiate before evaluating: 0 for
betaitself, 1 ford beta / d omega. Non-negative.- splinescipy.interpolate.CubicSpline
The differentiated spline, built once in
__post_init__. Derived, so it is neither a constructor argument nor part of what a run records.
- Raises:
- ModeError
If the two arrays disagree in shape, the table is too short, its frequencies are not strictly increasing, or
derivativeis negative.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import TabulatedDispersion >>> omega = np.linspace(1.0e15, 3.0e15, 8) >>> beta = TabulatedDispersion(omega, 5.0e-9 * omega) >>> f"{float(beta(np.array([2.0e15]))[0]):.6f}" '10000000.000000'
- beta: Float[ndarray, 'n_beta_nodes']#
- omega: Float[ndarray, 'n_nodes']#
- spline: CubicSpline#
- class solprop.nonlinear.gnlse.TaylorDispersion(betas, omega_ref, derivative=0)[source]#
Bases:
objectbeta(omega), or a derivative of it, as a Taylor series about a frequency.beta(omega) = sum_n betas[n] (omega - omega_ref)**n / n!, sobetas[n]is the n-th derivative ofbetaat the reference frequency andbetas[1]is the inverse group velocity there. Differentiation is exact, in the polynomial basis, never by differencing, as the co-moving frame requires.The instance is callable and stands where a closure would. That is what makes a mode built from it reproducible in a run’s metadata:
solprop.output.schema.serialise()records a closure as itsrepr, which carries the function’s memory address, so a configuration holding one hashes differently in every process and the run could never be resumed from its own file. A frozen dataclass records its fields instead.Equality is by identity (
eq=False) becausebetasis an array.- Attributes:
- betasnumpy.ndarray
Taylor coefficients
beta_0, beta_1, ...in s^n/m, shape(n_deg,), at least one and all finite. A sequence is adopted as a read-only float64 array, so that two runs specified the same way record the same coefficients whichever type they passed and therefore hash alike.- omega_reffloat
Frequency the series is expanded about, rad/s, positive.
- derivativeint
How many times to differentiate before evaluating: 0 for
betaitself, 1 ford beta / d omega. Non-negative.- polynomialnumpy.polynomial.Polynomial
The differentiated series in the shifted variable
omega - omega_ref, built once in__post_init__. Derived, so it is neither a constructor argument nor part of what a run records.
- Raises:
- ModeError
If no coefficients are given, one is not finite,
omega_refis not positive, orderivativeis negative.
Examples
>>> import numpy as np >>> from solprop.nonlinear.gnlse import TaylorDispersion >>> beta = TaylorDispersion([1.0e7, 5.0e-9, -2.0e-26], 2.0e15) >>> f"{float(beta(np.array([2.0e15]))[0]):.6e}" '1.000000e+07' >>> gradient = TaylorDispersion([1.0e7, 5.0e-9], 2.0e15, 1) >>> f"{float(gradient(np.array([3.0e15]))[0]):.1e}" '5.0e-09'
- polynomial: Polynomial#
- solprop.nonlinear.gnlse.as_dispersion_path(value)[source]#
Coerce a frequency-only dispersion to a path, and pass a path through.
The spectral sibling of
solprop.profiles.as_profile(), and the one coercion: aSpecifiedModeasks for its dispersion, its derivative and its loss through this, so that every one of them answers the same four questions whether it moves along z or not.- Parameters:
- valuecallable or DispersionPath
A function of frequency alone, or a declared path already.
- Returns:
- DispersionPath
The path, a
ConstantDispersionwhen the argument is a plain function.
- Raises:
- ModeError
If a
solprop.profiles.ZProfileis given. Its members are a superset of this protocol’s, so it passes the structural test and would then fail insideSpecifiedMode.beta()with a rawTypeError; the two are different things and the refusal belongs at the boundary.
Examples
>>> from solprop.nonlinear.gnlse import TaylorDispersion, as_dispersion_path >>> as_dispersion_path(TaylorDispersion([1.0e7], 2.0e15)).is_constant True
- solprop.nonlinear.gnlse.attenuation_from_db(loss_db_per_m)[source]#
Convert a power loss in dB/m to a power attenuation coefficient in 1/m.
alpha = ln(10)/10 * loss, so that the power falls asexp(-alpha z)and the field amplitude asexp(-alpha z / 2), which is the-alpha/2of the linear operator (docs/dev/conventions.mdsection 2).- Parameters:
- loss_db_per_mfloat
Power loss in dB/m. Negative values describe gain and are permitted.
- Returns:
- float
Power attenuation coefficient in 1/m.
Examples
>>> from solprop.nonlinear.gnlse import attenuation_from_db >>> f"{attenuation_from_db(10.0):.6f}" '2.302585'
- solprop.nonlinear.gnlse.kerr_susceptibility(n2, raman_fraction=0.0)[source]#
Return the Kerr susceptibility of the GNLSE mapping.
chi3 = (4/3) (1 - f_R) n2 eps0 c: the4/3undoes the3/4of the envelope Kerr response, and the linear index is taken as one, which issolprop.physdata.nonlinear_data.chi3_from_n2()with a unit index.raman_fractionis the share of the nonlinearity a Raman response carries;problem_from()passes zero when Raman is off, and the share it does pass goes to a response of integralraman_susceptibility()times that fraction, so that the two together carry the whole ofgamma.- Parameters:
- n2float
Nonlinear index in m^2/W.
- raman_fractionfloat, optional
Share of the nonlinearity reserved for Raman, in
[0, 1]. Default 0.
- Returns:
- float
Third-order susceptibility in m^2/V^2.
- Raises:
- ModeError
If
raman_fractionlies outside[0, 1].
Examples
>>> from solprop.nonlinear.gnlse import kerr_susceptibility >>> f"{kerr_susceptibility(2.6e-20):.6e}" '9.201985e-23' >>> f"{kerr_susceptibility(2.6e-20, 0.18) / kerr_susceptibility(2.6e-20):.2f}" '0.82'
- solprop.nonlinear.gnlse.problem_from(betas, *, omega_ref, gamma=None, n2=None, aeff=1.0, loss_db_per_m=0.0, raman=False, raman_fraction=None, shock=True, selection=None)[source]#
Map GNLSE parameters onto a mode-averaged envelope problem.
The mapping:
k0 = omega_ref / candn2 = gamma A_eff / k0, withA_eff = 1unless one is given, so thatgamma = n2 omega_ref / (c A_eff)either way;chi3 = (4/3) (1 - f_R) n2 eps0 c, the4/3undoing the3/4of the envelope Kerr response and the linear index taken as one;with
raman=True, a fused-silicasolprop.nonlinear.raman.RamanResponseof integralraman_susceptibility()and fractionf_Rbeside it, so that the two together carry the whole ofgamma;the linear operator sees
beta(omega)directly and the prefactor seesn_eff = 1, which follows fromn_eff = c beta / omegaonly in the sense that the GNLSE’sbetaalready contains the whole dispersion;the shock term is the
omega / omega_refdependence of that prefactor rather than a time derivative, soshock=Falsesimply freezes it.
With
raman=Falseand no raman_fraction the(1 - f_R)factor is not applied, so none of the Kerr nonlinearity is discarded: the textbook “no Raman”,f_R = 0at fixedgamma. Withraman=Falseand an explicit raman_fraction the factor is applied and nothing carries the rest, so a pair of runs that gives the same fraction and switches only raman differs by the delayed response alone.- Parameters:
- betasarray_like
Taylor coefficients of
betaaboutomega_ref,beta_0, beta_1, ...in s^n/m.beta_0andbeta_1only shift the frame and do not change the physics.- omega_reffloat
Reference angular frequency, rad/s, positive. Both the Taylor expansion and the
gammamapping refer to it.- gammafloat or None, optional
Nonlinear coefficient in 1/(W m). Give this or n2, not both.
- n2float, str or None, optional
Nonlinear index in m^2/W. Give this or gamma, not both.
"recommended"takes the silica bundle’s total long-pulsen2(optmat’skerr/SiO2) at the carrier2 pi c / omega_ref.- aefffloat, optional
Effective area in m^2. Default 1.0, the GNLSE convention in which the area is already folded into
gamma.- loss_db_per_mfloat, optional
Power loss in dB/m, converted by
attenuation_from_db(). Default 0.- ramanbool, optional
Whether a Raman response takes the share raman_fraction of
gamma. DefaultFalse.- raman_fractionfloat or None, optional
Share of
gammaheld back from the Kerr term, between 0 and 1.None, the default, is the silica bundle’s ownf_Rwith raman true – 0.2188 for optmat’s default bundle (SEP 3, D4) – and 0 with it false, which gives the whole ofgammato the Kerr term. A value given is always held back, with raman true or false, and overrides the bundle’s.- shockbool, optional
Keep the
omegadependence of the normalisation prefactor. DefaultTrue.- selectionsolprop.physdata.selection.MaterialSelection or None, optional
NoneisMaterialSelection(). Fused silica’s nonlinear response is optmat’s silica bundle (SEP 3, section 4.5): the bundle named byselection.raman("recommended"is the"default"bundle, Hollenbeck and Cantrell’s shape with the Kerr fit’sf_R; another name is an optmat bundle name, an object is taken as it is), itsf_Rfor an omitted fraction with Raman on, its normalisedh_Rsampled as the Raman response, and itsn2forn2="recommended".
- Returns:
- GnlseProblem
The mode, the Kerr response and the coefficients they came from.
- Raises:
- ModeError
If neither or both of gamma and n2 are given, if raman_fraction is outside
[0, 1], if omega_ref or aeff is not positive, if raman is true with a zero fraction, or if n2 is a string other than"recommended".
Notes
raman=Truewithraman_fraction=0raises rather than returning a problem whose Raman response carries none of the nonlinearity: the response itself refuses a fraction outside(0, 1], and a run that declared a term contributing nothing would pay for a convolution at every stage and be indistinguishable from one that did not declare it.Examples
>>> from solprop.nonlinear.gnlse import problem_from >>> problem = problem_from([0.0, 5.0e-9], omega_ref=1.8e15, gamma=0.1, aeff=1.0) >>> f"{problem.n2:.6e}", f"{problem.chi3:.6e}" ('1.665514e-08', '5.894628e-11') >>> raman = problem_from([0.0, 5.0e-9], omega_ref=1.8e15, gamma=0.1, raman=True) >>> f"{raman.chi3 / problem.chi3:.2f}", f"{raman.raman_fraction:.4f}" ('0.78', '0.2188') >>> held = problem_from( ... [0.0, 5.0e-9], omega_ref=1.8e15, gamma=0.1, raman_fraction=0.18 ... ) >>> f"{held.chi3 / problem.chi3:.2f}", held.raman is None ('0.82', True)
- solprop.nonlinear.gnlse.raman_susceptibility(n2)[source]#
Return the Raman strength of the GNLSE mapping, as an integrated response.
A Raman response is stated by its integral:
integral h dtcarries the units ofsolprop.nonlinear.responses.KerrResponse.chi3per unit density (docs/guide/media/raman-numerics.md), so it is directly comparable with the Kerr susceptibility. The two are not equal for the same share of the nonlinearity, because the envelope forms differ by their numerical factors:\[P_\text{Kerr} = \tfrac34\,\varepsilon_0\chi^{(3)}\lvert A\rvert^2 A, \qquad P_\text{Raman} \to \tfrac12\,\varepsilon_0 \left(\textstyle\int h\,\mathrm{d}t\right)\lvert A\rvert^2 A\]in the slowly varying limit, so a Raman term of integral
Xacts like a Kerr term of(2/3) Xand carrying the whole nonlinearity needsX = (3/2) chi3_total = 2 n2 eps0 c. A response built with this amplitude and afractionoff_Rtherefore takes exactly the sharekerr_susceptibility()leaves behind, and(2/3) f_R raman_susceptibility(n2) + kerr_susceptibility(n2, f_R) == kerr_susceptibility(n2)holds by construction, which is a test. Because the response function here is linear in
eps0, no furthereps0appears; a convention with a response quadratic ineps0writes the same number aschi3R = 2 f_R n2 eps0 c.- Parameters:
- n2float
Nonlinear index in m^2/W.
- Returns:
- float
The value
integral h dttakes when the whole nonlinearity is Raman, in m^2/V^2; asolprop.nonlinear.raman.RamanResponsescales it by its ownfraction.
Examples
>>> from solprop.nonlinear.gnlse import kerr_susceptibility, raman_susceptibility >>> f"{raman_susceptibility(2.6e-20):.6e}" '1.380298e-22' >>> total = kerr_susceptibility(2.6e-20) >>> share = (2 / 3) * 0.18 * raman_susceptibility(2.6e-20) >>> bool(abs(share + kerr_susceptibility(2.6e-20, 0.18) - total) < 1e-36) True