solprop.nonlinear.raman#
Raman response models and the reference evaluation of the Raman polarisation.
A Raman response is a medium’s delayed answer to the field: the molecular vibration
or rotation the pulse drives keeps ringing after the pulse has gone, and the
polarisation at time t depends on the whole history of the drive. It is therefore a
convolution, where the Kerr response of solprop.nonlinear.responses is pointwise,
and that difference is the whole of why this module exists separately.
The models here are a damped oscillator, the
vibrational mode of a molecular gas, the non-rigid-rotor rotational ladder with its
Boltzmann populations, the intermediate-broadening line set of an amorphous solid, and
a shape sampled from an optmat silica bundle. Each is a frozen dataclass returning
h(t) for t >= 0, and each declares how its coherence dephases with density.
The molecular and line-set models take a caller’s own constants
(solprop.physdata.raman_types); every built-in gas’s response is optmat’s
(solprop.nonlinear.optmat_raman, SEP 3) and fused silica’s is its bundle.
Beside them is SilicaTensorResponse: the delayed
tensor response of fused silica declared by a material region of a general modal
guide, whose two channels are a share of the region’s own chi3 rather than a
per-density response, and whose kernels are therefore in 1/s and not in the units
below.
Units. Every model of a gas or a scalar route returns h(t) in
m^5 V^-2 s^-1, the units that make
hold with P in C/m^2, the number density rho in m^-3 and the field E in V/m.
So integral h dt carries the units of
solprop.nonlinear.responses.KerrResponse.chi3 per unit density, m^5/V^2, and the
Raman and Kerr responses of the same medium are directly comparable. The eps0
stands outside the integral, so h here is linear in eps0; a convention whose
outer expression has no eps0 hides it inside every K as (4 pi eps0)^2 and
has an h quadratic in eps0, larger by that factor. The scalar vibrational factor
also includes the independent amplitude correction described in vibrational().
The manual page is docs/guide/media/raman.md; the kernel’s view of the response is
docs/dev/kernel-contract.md section 5, and the sampling onto the doubled grid is
solprop.translate.raman.
Module Attributes
Levels a rotational ladder needs before one |
|
What the two normalised shapes of |
|
The field-domain amplitude factor of |
|
Spellings |
Functions
|
Build fused silica's Raman response from an optmat bundle. |
|
Build a damped oscillator whose response integrates to a given value. |
|
Return the real quantity the Raman response is driven by. |
|
Build the Raman response of a named material. |
|
Build the rotational Raman spectrum of a linear molecule. |
Return the dephasing of a coherence that does not decay with density. |
|
|
Build the Raman response of a molecular vibration. |
Classes
|
A single Raman line as a damped harmonic oscillator. |
|
The Raman response of an amorphous solid, as intermediately broadened lines. |
|
One term of a Raman response: an undamped shape and a density-dependent decay. |
|
The delayed third-order polarisation of a Raman-active medium. |
|
Declare how one scalar linear-polarisation kernel contracts a vector field. |
|
The rotational Raman spectrum of a linear molecule, line by line. |
|
A density-independent Raman term sampled from optmat's silica bundle. |
|
The delayed tensor Raman response of fused silica (Lin--Agrawal). |
- class solprop.nonlinear.raman.DampedOscillator(amplitude, frequency, dephasing)[source]#
Bases:
objectA single Raman line as a damped harmonic oscillator.
h(t) = amplitude * sin(frequency * t) * exp(-t / tau2)fort > 0and zero otherwise: the impulse response of a driven oscillator in the weak-damping limit, and the model behind every molecular vibration and every rotational line.- Attributes:
- amplitudefloat
Scale factor
K, in m^5 V^-2 s^-1, the units ofhitself. It may be negative: a rotational line’s weight changes sign with the population difference it comes from.- frequencyfloat
Angular frequency of the transition, rad/s, positive.
- dephasingDephasing
How the coherence decays with the local number density.
- Raises:
- MaterialError
If the frequency is not positive and finite.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import DampedOscillator, undamped_dephasing >>> line = DampedOscillator(2.0, np.pi / 2.0, undamped_dephasing()) >>> line.undamped(np.array([-1.0, 0.0, 1.0])).tolist() [0.0, 0.0, 2.0]
- undamped(t)[source]#
Return the undamped response on a time axis, in m^5 V^-2 s^-1.
- Parameters:
- tnumpy.ndarray
Delays, s, shape
(n_to,).
- Returns:
- numpy.ndarray
amplitude * sin(frequency * t)wheret > 0and zero elsewhere, shape(n_to,).
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import DampedOscillator, undamped_dephasing >>> line = DampedOscillator(1.0, 1.0, undamped_dephasing()) >>> float(line.undamped(np.array([np.pi / 2.0]))[0]) 1.0
- class solprop.nonlinear.raman.Dephasing(linewidth)#
Bases:
objectThe decay of a Raman coherence, as a function of the local number density.
A wrapper over
solprop.physdata.raman_types.Linewidththat turns a full width at half maximum in hertz into the decay factorexp(-t / tau2)a response is damped by, and that is defined at every density including zero.The zero-density limit has three cases, and which one applies is fixed by the linewidth’s own coefficients:
a gas with Dicke narrowing (
inverse_density > 0) has a diverging linewidth as the density falls, sotau2 -> 0: no coherence at all;a gas with only collisional broadening (
constantandinverse_densityboth zero) has a vanishing linewidth, sotau2 -> inf: an undamped coherence;a gas with a density-independent term keeps
tau2 = 1 / (pi * constant).
All three are finite answers, for the vibrational model as for the rotational one, so a fill that starts from vacuum gives no NaN polarisation. The density multiplies the polarisation anyway, so the choice is invisible in the answer and visible only in whether the answer is a number.
- Attributes:
- linewidthLinewidth
The dephasing rate of the transition, in Hz per its own density terms.
Examples
>>> from solprop.constants import AMAGAT >>> from solprop.nonlinear.raman import Dephasing >>> from solprop.physdata import Linewidth >>> dephasing = Dephasing(Linewidth(density=3.3e9)) >>> f"{dephasing.dephasing_time(AMAGAT):.4e}" '9.6458e-11' >>> dephasing.dephasing_time(0.0) inf
- damping(t, density)#
Return the decay factor
exp(-t / tau2)on a time axis.- Parameters:
- tnumpy.ndarray
Times since the drive, s, shape
(n_to,), non-negative.- densityfloat
Number density in m^-3, non-negative.
- Returns:
- numpy.ndarray
The factor in
[0, 1], shape(n_to,). All ones for an undamped coherence and all zeros where the coherence has collapsed, rather than anexp(-0/0)NaN.
- Raises:
- MaterialError
If the density is negative.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import Dephasing >>> from solprop.physdata import Linewidth >>> dephasing = Dephasing(Linewidth(inverse_density=6.15e6)) >>> dephasing.damping(np.array([0.0, 1e-12]), 0.0).tolist() [0.0, 0.0]
- decay_rate(density)#
Return the coherence decay rate at a number density.
The reciprocal of
dephasing_time(), and the form the translation layer prefers: zero is the undamped limit and is an ordinary float, where the dephasing time it corresponds to is infinite.- Parameters:
- densityfloat
Number density in m^-3, non-negative.
- Returns:
- float
1 / tau2in s^-1. Zero for an undamped coherence andinfwhere the Dicke term diverges at zero density.
- Raises:
- MaterialError
If the density is negative.
Examples
>>> from solprop.nonlinear.raman import Dephasing >>> from solprop.physdata import Linewidth >>> Dephasing(Linewidth(inverse_density=6.15e6)).decay_rate(0.0) inf
- dephasing_time(density)#
Return the coherence dephasing time at a number density.
- Parameters:
- densityfloat
Number density in m^-3, non-negative.
- Returns:
- float
tau2in s.inffor an undamped coherence and0.0where the Dicke term diverges at zero density.
- Raises:
- MaterialError
If the density is negative.
Examples
>>> from solprop.nonlinear.raman import Dephasing >>> from solprop.physdata import Linewidth >>> Dephasing(Linewidth(constant=8220e6)).dephasing_time(0.0) 3.872...e-11
- class solprop.nonlinear.raman.IntermediateBroadening(lines, amplitude=1.0)[source]#
Bases:
objectThe Raman response of an amorphous solid, as intermediately broadened lines.
Each line of the set contributes a damped sinusoid with both a homogeneous (Lorentzian) and an inhomogeneous (Gaussian) width,
\[h(t) \propto \sum_i A_i \mathrm{e}^{-\gamma_i t} \mathrm{e}^{-\Gamma_i^2 t^2/4}\sin(\omega_i t),\]the Hollenbeck-Cantrell model of fused silica. The widths are the dephasing, so the response does not depend on density and
dephasingis the trivial one; a glass is a fixed medium and itsdensitypath is one.The amplitudes of the line set are relative, so the model is normalised: its integral is exactly
amplitude, which is therefore the Raman contribution to the medium’s third-order susceptibility in m^5/V^2 per unit of the density argument.The normalisation is analytic. A numerical quadrature of the oscillatory sum would need to reach out to about 1 ns over an integrand supported in its first 0.1 % and crossing zero some thousands of times, at the mercy of the quadrature’s tolerance. Here
\[\int_0^\infty \mathrm{e}^{-\gamma t}\mathrm{e}^{-\Gamma^2t^2/4}\sin(\omega t) \,dt = \mathrm{Im}\left[\frac{\sqrt{\pi}}{\Gamma}\, w\!\left(\frac{\omega + \mathrm{i}\gamma}{\Gamma}\right)\right],\]with
wthe Faddeeva functionscipy.special.wofz, which is the stable spelling ofexp(z^2) erfc(z)in the half plane these arguments occupy.- Attributes:
- linesIntermediateBroadeningRaman
The line set, the caller’s own; fused silica’s built-in response is its optmat bundle (
bundle_response()).- amplitudefloat, optional
The value
integral h dtis to take, m^5/V^2 per unit density. Default 1.0, which makes the model a pure shape.
- Raises:
- MaterialError
If the line set integrates to zero, so that it cannot be normalised.
References
D. Hollenbeck and C. D. Cantrell, “Multiple-vibrational-mode model for fiber-optic Raman gain spectrum and response function”, J. Opt. Soc. Am. B 19, 2886 (2002), doi:10.1364/JOSAB.19.002886.
Examples
>>> from solprop.nonlinear.raman import IntermediateBroadening >>> from solprop.physdata import IntermediateBroadeningRaman >>> glass = IntermediateBroadening( ... IntermediateBroadeningRaman( ... frequencies=(8.2e13,), ... amplitudes=(1.0,), ... gaussian_widths=(9.6e12,), ... lorentzian_widths=(3.0e12,), ... citation="one illustrative line", ... ) ... ) >>> f"{glass.scale:.9e}" '8.154299442e+13'
- property dephasing: Dephasing[source]#
Return the density-dependent dephasing, which for a glass is none.
- Returns:
- Dephasing
A dephasing whose factor is one everywhere; the Lorentzian widths of
linesalready carry the decay.
- lines: IntermediateBroadeningRaman#
- property raw_integral: float[source]#
Return the integral of the unnormalised line sum over the positive axis.
- Returns:
- float
sum_i A_i Im[(sqrt(pi)/Gamma_i) w((omega_i + i gamma_i)/Gamma_i)], in the units of the tabulated amplitudes times seconds.
Examples
>>> from solprop.nonlinear.raman import IntermediateBroadening >>> from solprop.physdata import IntermediateBroadeningRaman >>> glass = IntermediateBroadening( ... IntermediateBroadeningRaman( ... frequencies=(8.2e13,), ... amplitudes=(1.0,), ... gaussian_widths=(9.6e12,), ... lorentzian_widths=(3.0e12,), ... citation="one illustrative line", ... ) ... ) >>> f"{glass.raw_integral:.9e}" '1.226346919e-14'
- property scale: float[source]#
Return the factor the raw line sum is multiplied by.
- Returns:
- float
amplitudedivided byraw_integral, so that the response integrates toamplitude.
- Raises:
- MaterialError
If the line set integrates to zero.
- undamped(t)[source]#
Return the normalised response on a time axis, in m^5 V^-2 s^-1.
Named
undampedfor theRamanModelprotocol, but the Lorentzian and Gaussian widths of the line set are already in it: what the name excludes is the density-dependent decay, and a glass has none.- Parameters:
- tnumpy.ndarray
Delays, s, shape
(n_to,).
- Returns:
- numpy.ndarray
The response where
t > 0and zero elsewhere, shape(n_to,).
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import IntermediateBroadening >>> from solprop.physdata import IntermediateBroadeningRaman >>> glass = IntermediateBroadening( ... IntermediateBroadeningRaman( ... frequencies=(8.2e13,), ... amplitudes=(1.0,), ... gaussian_widths=(9.6e12,), ... lorentzian_widths=(3.0e12,), ... citation="one illustrative line", ... ) ... ) >>> bool(np.all(glass.undamped(np.array([-1e-15, 0.0])) == 0.0)) True
- solprop.nonlinear.raman.MIN_ROTATIONAL_LEVELS: Final = 3#
Levels a rotational ladder needs before one
J -> J + 2pair exists.
- class solprop.nonlinear.raman.MolecularOptions(temperature=None, rotation=None, vibration=None, min_j=0, max_j=50)#
Bases:
objectWhich parts of a molecular gas’s Raman spectrum to build, and at what state.
Grouped because
solprop.nonlinear.raman.response_for()would otherwise take more arguments than the project allows loose (AGENTS.md, “Functions and modules”).Each term flag has three states:
None(the default) takes the term when the species has a model for it and silently goes without otherwise,Truerequires it and is refused by species name when no model exists,Falseleaves it out. SoMolecularOptions()means every term the species has, and only a term the caller named can fail. The temperature likewise defaults to the run’s or the mixture’s own, and tosolprop.constants.ROOM_TEMPERATUREfor a baresolprop.nonlinear.raman.response_for()call.- Attributes:
- temperaturefloat or None, optional
Gas temperature in K, positive; sets the rotational populations.
Nonetakes the temperature of the run or mixture the options are used in.- rotationbool or None, optional
Whether to include the rotational ladder.
Nonetakes it when available.- vibrationbool or None, optional
Whether to include the vibrational mode.
Nonetakes it when available.- min_jint, optional
Lowest rotational quantum number, non-negative. Default 0.
- max_jint, optional
Highest rotational quantum number before truncation. Default 50.
Examples
>>> from solprop.nonlinear.raman import MolecularOptions >>> MolecularOptions(rotation=False).vibration is None True >>> MolecularOptions().resolved(300.0).temperature 300.0
- resolved(temperature)#
Return the options with an unset temperature taken from the caller.
- Parameters:
- temperaturefloat
The run’s or mixture’s temperature in K.
- Returns:
- MolecularOptions
The same options, with the temperature filled in when it was
None.
- class solprop.nonlinear.raman.RamanModel(*args, **kwargs)[source]#
Bases:
ProtocolOne term of a Raman response: an undamped shape and a density-dependent decay.
The split is what lets the translation layer sample the expensive part once per grid and recompute only the cheap exponential when the density changes along a pressure gradient. A model is responsible for the part of
h(t)that does not move with density;dephasingsupplies the rest.- property dephasing: Dephasing[source]#
How this term’s coherence decays with the local number density.
- undamped(t)[source]#
Return
h(t)without the density-dependent decay, in m^5 V^-2 s^-1.Zero before
t = 0, so that the response is causal. Att = 0it is zero for a response that rises continuously from zero; a response that jumps there takes half its right-hand limit, the trapezoid endpoint of the causal convolution.- Parameters:
- tnumpy.ndarray
Delays since the drive, s, shape
(n_to,).
- Returns:
- numpy.ndarray
The undamped response, m^5 V^-2 s^-1, shape
(n_to,).
- class solprop.nonlinear.raman.RamanResponse(models=None, fraction=None, thg=True, species=None, *, tensor_terms=None)[source]#
Bases:
objectThe delayed third-order polarisation of a Raman-active medium.
The scalar reduction is
P(t) = eps0 rho E(t) integral h(t - t') E^2(t') dt', withhthe sum of theRamanModelterms and the drive chosen per grid byraman_drive(). The convolution is what makes this response different in kind from the Kerr one: it has memory, so a backend evaluates it with a transform pair rather than sample by sample, andsolprop.translate.ramanprepares the kernel for that.Build one with
response_for()rather than by hand, unless the models are yours. A tensor declaration couples the local Cartesian components through the isotropic trace and three-dimensional traceless molecular responses. Envelope drives useRe(A A†)/2; beat-removed fields use their analytic signals for that drive and the real field for the final multiplication.- Parameters:
- modelstuple of RamanModel or None, optional
Legacy scalar-only kernels, in summation order. Supply exactly one nonempty route:
modelsor keyword-onlytensor_terms.- fractionfloat or None, optional
Overall Raman share in
(0, 1], orNonefor absolute kernels.- thgbool, optional
Retain the real-field harmonic drive. Default True; ignored for envelopes.
- speciesstr or None, optional
Label selecting the amplitude density in a mixture.
- tensor_termstuple of RamanTensorTerm or None, optional
Explicit labelled kernels, preserving each term’s own dephasing.
- Attributes:
- tensor_termstuple of RamanTensorTerm
The canonical immutable declaration. Supply this keyword or legacy
models, exactly one nonempty route. Legacy models become scalar-only terms. Each term retains its own dephasing and explicit tensor assignment.modelstuple of RamanModelReturn the scalar models in declaration order.
- fractionfloat or None, optional
The Raman fraction
f_R, in(0, 1], multiplying the whole response, orNonewhen the models are already absolute – which every molecular model here is. It exists for a medium whose Raman strength is stated as a share of its total nonlinearity, as a fibre GNLSE does. DefaultNone.- thgbool, optional
Whether a field grid keeps the third harmonic of the drive. Ignored on an envelope grid. Default
True.- speciesstr or None, optional
Component label whose density sets this response’s amplitude.
- Raises:
- MaterialError
If there are no models, or the fraction is outside
(0, 1].
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import response_for >>> from solprop.physdata import Gas, MaterialSelection >>> selection = MaterialSelection(carrier_wavelength=800e-9) >>> response = response_for(Gas.N2, selection=selection) >>> response.traits.domain 'convolution' >>> len(response.models) 72
- evaluate(field_t, density, *, kind, dt)[source]#
Return the Raman polarisation on the fine time grid.
The reference implementation, and therefore the oracle for every backend. It allocates, transforms twice on a grid of twice the length, and rebuilds the kernel on every call;
solprop.translate.ramandoes none of those things.The convolution runs on a doubled, zero-padded grid. A circular convolution on the fine grid alone would wrap the tail of the response back onto the head of the drive, and for a gas whose rotational coherence outlives the window – nanoseconds against tens of picoseconds – that wrapped tail is not small. The doubled grid makes the transform product the linear convolution exactly. The
dtscaling is the sample spacing the discrete sum needs to become the integral; the inverse transform’s1/Nsupplies the rest.- Parameters:
- field_tnumpy.ndarray
Finite local Cartesian field, shape
(n_pol, n_to), with one or two components: realE(t)in V/m on a field grid, complexA(t)in V/m on an envelope grid. Two components require tensor-capable terms.- densityfloat
Number density in m^-3, non-negative. It both scales the polarisation and sets the dephasing.
- kind{‘field’, ‘envelope’}
The spectral layout the samples belong to.
- dtfloat
Sample spacing of the fine time grid, s, positive.
- Returns:
- numpy.ndarray
The polarisation in C/m^2, shape
(n_pol, n_to), real on a field grid and complex on an envelope grid.
- Raises:
- GridError
If the layout is unknown, the shape or dtype does not match it, the sample spacing is not positive, samples are non-finite, or a scalar-only term is evaluated on a vector field.
- MaterialError
If the density is negative or non-finite.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import response_for >>> from solprop.physdata import Gas, MaterialSelection >>> selection = MaterialSelection(carrier_wavelength=800e-9) >>> t = (np.arange(64) - 32) * 1e-15 >>> field = (1e9 * np.exp(-((t / 5e-15) ** 2)))[None, :] >>> response = response_for(Gas.N2, selection=selection) >>> out = response.evaluate(field, 1e25, kind="field", dt=1e-15) >>> out.shape, out.dtype ((1, 64), dtype('float64'))
- impulse(t, density)[source]#
Return the response function
h(t)at one density, in m^5 V^-2 s^-1.Each model is damped by its own dephasing before the sum, which is what a gas with both a rotational and a vibrational coherence requires: at one bar those two decay 3.5 times (H2) to 17 times (N2) apart.
- Parameters:
- tnumpy.ndarray
Delays, s, shape
(n_to,). Zero and negative delays give zero.- densityfloat
Number density in m^-3, non-negative.
- Returns:
- numpy.ndarray
h(t), m^5 V^-2 s^-1, shape(n_to,).
- Raises:
- MaterialError
If the density is negative.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import response_for >>> from solprop.physdata import Gas, MaterialSelection >>> selection = MaterialSelection(carrier_wavelength=800e-9) >>> response = response_for(Gas.N2, selection=selection) >>> bool(np.all(response.impulse(np.array([-1e-15, 0.0]), 1e25) == 0.0)) True
- property models: tuple[RamanModel, ...][source]#
Return the scalar models in declaration order.
- Returns:
- tuple of RamanModel
A derived compatibility view; replace
tensor_termsto update models.
- property scale: float[source]#
Return the overall multiplier the response carries.
- Returns:
- float
fraction, or 1.0 when it isNone.
- property tensor_capable: bool[source]#
Return whether every term has an established vector contraction.
- Returns:
- bool
False if any term is declared
scalar_only.
- tensor_impulse(t, density)[source]#
Return the isotropic and rank-two kernels in that order.
These are the scalar linear-polarisation kernels
h_vandh_r, before multiplication byeps0 * density. Damping and the declared fraction are included. Their sum equalsimpulse()up to rounding.- Parameters:
- tnumpy.ndarray
Finite real delays in s, shape
(n_to,); nonpositive delays give zero.- densityfloat
Finite non-negative number density in m^-3, setting the damping.
- Returns:
- numpy.ndarray
Shape
(2, n_to), in m^5 V^-2 s^-1. Row zero is isotropic; row one is rank two. A channel with no declared terms is zero.
- Raises:
- MaterialError
If any term is scalar-only or the density is invalid.
- GridError
If delays are not a finite real one-dimensional array.
- tensor_terms: tuple[RamanTensorTerm, ...]#
- property traits: ResponseTraits[source]#
Return the declared traits of the Raman response.
- Returns:
- ResponseTraits
A convolution on both grids consuming the density path. Tensor-capable declarations couple Cartesian components; other declarations are scalar.
Examples
>>> from solprop.nonlinear.raman import response_for >>> response_for("SiO2").traits.polarisation 'scalar'
- class solprop.nonlinear.raman.RamanTensorTerm(model, contraction)[source]#
Bases:
objectDeclare how one scalar linear-polarisation kernel contracts a vector field.
A model’s class does not determine its tensor character. In particular, a custom oscillator needs an explicit physical assignment before vector use.
- Attributes:
- modelRamanModel
Scalar kernel in m^5 V^-2 s^-1, with its own dephasing law.
- contraction{‘scalar_only’, ‘isotropic’, ‘rank2’}
No established vector contraction, the isotropic trace channel, or the three-dimensional traceless rank-two channel, respectively. Each channel reduces to
modelfor a fixed real linear polarisation.
- Raises:
- MaterialError
If the model does not implement the Raman protocol or the label is unknown.
- model: RamanModel#
- class solprop.nonlinear.raman.RotationalLines(quantum_numbers, energies, populations, frequencies, amplitudes, dephasing, temperature)[source]#
Bases:
objectThe rotational Raman spectrum of a linear molecule, line by line.
A non-rigid rotor’s levels are
E_J = 2 pi hbar c (B J(J+1) - D (J(J+1))^2)and the Raman-active transitions are theSbranch,J -> J + 2. Each pair contributes a damped oscillator whose weight is set by the population difference between its two levels, so a line can have either sign, and the whole ladder shares one dephasing: theJdependence of the linewidth is dropped.Build one with
rotational_nonrigid(); the arrays are stored so that a test or a manual page can look at the ladder itself rather than only at its sum.- Attributes:
- quantum_numbersnumpy.ndarray
The rotational quantum numbers
Jretained, shape(n_levels,), integer.- energiesnumpy.ndarray
Level energies in J, shape
(n_levels,).- populationsnumpy.ndarray
Fractional level populations, shape
(n_levels,), summing to one.- frequenciesnumpy.ndarray
Transition angular frequencies of the
J -> J + 2pairs, rad/s, shape(n_lines,)withn_lines = n_levels - 2.- amplitudesnumpy.ndarray
Line scale factors in m^5 V^-2 s^-1, shape
(n_lines,). Signed.- dephasingDephasing
The dephasing shared by every line.
- temperaturefloat
The temperature the populations were computed at, K.
- Raises:
- MaterialError
If the array lengths do not agree, or there are fewer than three levels.
Examples
>>> from solprop.nonlinear.raman import rotational_nonrigid >>> from solprop.physdata import Linewidth, RotationalRaman >>> nitrogen = RotationalRaman( ... b=199.0, ... d=5.74e-4, ... degeneracy_odd=1, ... degeneracy_even=2, ... polarisability_anisotropy=6.7e-31, ... linewidth=Linewidth(density=3.3e9), ... citation="a caller's N2 constants", ... ) >>> lines = rotational_nonrigid(nitrogen) >>> lines.frequencies.size == lines.quantum_numbers.size - 2 True >>> f"{float(lines.populations.sum()):.15f}" '1.000000000000000'
- amplitudes: Float[ndarray, 'n_amplitudes']#
- energies: Float[ndarray, 'n_energies']#
- frequencies: Float[ndarray, 'n_lines']#
- property n_lines: int[source]#
Return the number of
J -> J + 2transitions.- Returns:
- int
Two fewer than the number of levels.
- populations: Float[ndarray, 'n_populations']#
- quantum_numbers: Int[ndarray, 'n_levels']#
- undamped(t)[source]#
Return the undamped response of the whole ladder, in m^5 V^-2 s^-1.
- Parameters:
- tnumpy.ndarray
Delays, s, shape
(n_to,).
- Returns:
- numpy.ndarray
sum_i amplitude_i sin(frequency_i t)wheret > 0and zero elsewhere, shape(n_to,).
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import rotational_nonrigid >>> from solprop.physdata import Linewidth, RotationalRaman >>> hydrogen = RotationalRaman( ... b=5890.0, ... d=5.0, ... degeneracy_odd=3, ... degeneracy_even=1, ... polarisability_anisotropy=3e-31, ... linewidth=Linewidth(inverse_density=6.15e6, density=114e6), ... citation="a caller's H2 constants", ... ) >>> lines = rotational_nonrigid(hydrogen) >>> bool(np.all(lines.undamped(np.array([-1e-12, 0.0])) == 0.0)) True
- solprop.nonlinear.raman.SILICA_NAMES: Final = ('sio2', 'silica', 'fused_silica', 'fused silica')#
Spellings
response_for()accepts for fused silica.
- solprop.nonlinear.raman.SILICA_TENSOR_AMPLITUDE: Final = 1.5#
The field-domain amplitude factor of
SilicaTensorResponse’s kernels.f_Randn2are envelope (Agrawal) quantities:f_Ris the share of the effective nonlinearity on the fundamental that the delayed term carries. The kernels here act in the field domain, where the delayed polarisationeps0 chi3 E int h(tau) E^2(t - tau) dtauis driven by the cycle average|A|^2/2and so contributes only(1/2) (int h) |A|^2 Aon the fundamental, against the instantaneous cubic’s(3/4) chi3 |A|^2 A. A field-domain kernel of integralXtherefore acts like a Kerr term of(2/3) X, and carrying the sharef_Rof onechi3needsint h = (3/2) f_R chi3.This is exactly
solprop.nonlinear.gnlse.raman_susceptibility()’s2 n2 eps0 c = (3/2) chi3, the identity(2/3) f_R X + kerr_susceptibility(n2, f_R) == kerr_susceptibility(n2)it is built to satisfy: the(3/2) chi3_totalamplitude mapping, with the fraction applied exactly once. Without it the declaredn2comes outf_R/3– 8.2 % for silica – low.
- solprop.nonlinear.raman.SILICA_TENSOR_INTEGRALS: Final = (0.8333333333333334, 0.16666666666666666)#
What the two normalised shapes of
SilicaTensorResponseintegrate to.These are the shape integrals, not the kernel integrals: the kernels carry the amplitude
SILICA_TENSOR_AMPLITUDEtimesf_Ron top of them, so they integrate to(3/2) f_Rtimes the pair below. The pair itself sums to one, which is what makes it a shape.Lin and Agrawal write the delayed polarisation as an isotropic term in \(a(\tau)\) and a dyadic term in \(b(\tau)\), with \(a = w_a h_a\) and \(b = w_b h_b + w_{ab} h_a\) (Opt. Lett. 31, 3086 (2006), Eq. (1); the vector theory is J. Opt. Soc. Am. B 20, 1616 (2003)). The record the seam carries instead splits the same polarisation into a trace channel and the three-dimensional traceless rank-two channel
T(Q) = (3Q - tr(Q) I)/2, which is the contraction the existing vector Raman route already uses. Matchinga tr(Q) E + b Q E = r_trace tr(Q) E + r_rank T(Q) Egives
r_trace = a + b/3andr_rank = 2 b/3. Becauseh_aandh_bare each normalised, the two integrate tor_trace = w_a + (w_b + w_ab)/3andr_rank = 2 (w_b + w_ab)/3,whose sum is
w_a + w_b + w_ab = 1. The values stored here, 5/6 and 1/6, are what those expressions give for Lin and Agrawal’s (2006) weights –w_a = 0.75andw_b + w_ab = 0.25– and for no others: a differentw_amoves both numbers. They are not the paper’s ownint a = 3/4andint b = 1/4, which belong to a different pair of channels.
- class solprop.nonlinear.raman.SampledShape(bundle, channel='parallel', amplitude=1.0)[source]#
Bases:
objectA density-independent Raman term sampled from optmat’s silica bundle.
The undamped row is the bundle’s normalised shape times amplitude, and the dephasing is none: a glass’s widths are already in its shape, and it has no density to scale (SEP 3, section 4.5). The channels:
"parallel": the copolarised \(h_R = a + b\), unit integral, for a scalar run (ascalar_onlyterm);"isotropic": \(h_0 = a + b/3\), the trace channel;"rank2": \(h_2 = 2b/3\), the traceless rank-two channel,
with Hellwarth’s \(a\) and \(b\) from
h_r(t, "a")andh_r(t, "b"). With the seam’s weights,(1, 0)for the isotropic term and(1/4, 3/4)for the rank-two one, a copolarised probe then sees \(a + b\) and a cross-polarised one \(a\).At zero delay the sample is half the shape’s right-hand limit, the trapezoid rule’s endpoint weight, and zero below it. That is exactly zero for a shape that opens with a sine (Hollenbeck and Cantrell’s, and \(a\)); for the boson peak in \(b\), whose limit is \(2 f_b/\tau_b\), it keeps the convolution second order where a zero sample would leave it first order, at no cost to causality, as
SilicaTensorResponse.kernels()explains.- Attributes:
- bundleSilicaBundle
The optmat silica bundle.
- channel{‘parallel’, ‘isotropic’, ‘rank2’}, optional
The part sampled. Default
"parallel".- amplitudefloat, optional
The value
integral h dttakes for the"parallel"channel, m^5/V^2 per unit density. Default 1.0, a pure shape.
- Raises:
- MaterialError
If the channel is unknown, the amplitude is not finite, or a tensor channel is asked of a copolarised-only bundle, naming it.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import SampledShape >>> from solprop.physdata.glasses import SilicaBundle >>> shape = SampledShape(SilicaBundle.resolve()) >>> t = np.arange(40000) * 5e-17 >>> f"{shape.undamped(t).sum() * 5e-17:.4f}" '1.0000'
- bundle: SilicaBundle#
- property dephasing: Dephasing[source]#
Return the density-dependent dephasing, which for a glass is none.
- Returns:
- Dephasing
A dephasing whose factor is one everywhere.
- property material_records: tuple[MaterialRecord, ...][source]#
Return the records of the bundle the term samples.
- Returns:
- tuple of MaterialRecord
The bundle’s, its Kerr fit’s and its shape’s.
- property name: str[source]#
Return the term’s name: the bundle’s and the channel.
- Returns:
- str
For example
"silica_bundle/default:parallel".
- shape(t)[source]#
Return the channel’s normalised shape, in 1/s, zero at and below zero delay.
- Parameters:
- tnumpy.ndarray
Delays in s, shape
(n_to,).
- Returns:
- numpy.ndarray
a + b,a + b/3or2 b/3, shape(n_to,).
- undamped(t)[source]#
Return the sampled term, in m^5 V^-2 s^-1.
- Parameters:
- tnumpy.ndarray
Delays in s, shape
(n_to,).
- Returns:
- numpy.ndarray
amplitudetimes the shape wheret > 0, half its right-hand limit att = 0and zero below, shape(n_to,).
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import SampledShape >>> from solprop.physdata.glasses import SilicaBundle >>> shape = SampledShape(SilicaBundle.resolve()) >>> shape.undamped(np.array([-1e-15, 0.0])).tolist() [0.0, 0.0]
- class solprop.nonlinear.raman.SilicaTensorResponse(parameters=<factory>)[source]#
Bases:
DisplayedThe delayed tensor Raman response of fused silica (Lin–Agrawal).
A material region’s declared delayed response, beside
solprop.modes.material.MaterialKerr: the two share one \(\chi^{(3)}\), of which this one takes the fraction \(f_R\) its parameters declare and the instantaneous Kerr term keeps \(1 - f_R\). It is a separately named model and changes nothing about the existing scalar silica response ofresponse_for(), which is a different fit (Hollenbeck–Cantrell) reached by a different route (a gas-filled or mode-averaged run’s response list).The polarisation. For the real three-component field \(\mathbf E(t)\), Lin and Agrawal’s delayed terms are
\[P_i^{\rm R}(t) = \varepsilon_0\chi^{(3)}\Big[ E_i(t)\!\int_0^\infty\! a(\tau)\, \mathbf E(t-\tau)\!\cdot\!\mathbf E(t-\tau)\,\mathrm d\tau + \sum_j E_j(t)\!\int_0^\infty\! b(\tau)\, E_i(t-\tau)E_j(t-\tau)\,\mathrm d\tau\Big],\]and this class carries them as the two channels
SILICA_TENSOR_INTEGRALSnames, over the six real symmetric pair histories \(Q_{kl} = E_kE_l\) of three components:P_i = eps0 chi3 sum_term sum_jkl D[term]_ijkl E_j (h[term] * Q)_kl,with
D[trace]_ijkl = d_ij d_klandD[rank]_ijkl = (3 (d_ik d_jl + d_il d_jk) - 2 d_ij d_kl)/4, the second beingT(Q)written as a contraction and symmetrised in its last two indices because a pair history is symmetric. The scalar limit is exact: for one fixed linear polarisation the two channels add toE(t) int (3/2) f_R h_R(tau) E^2(t - tau) dtauwithh_R = a + bintegrating to one – the standard fibre model this package’s scalar routes carry, at the field-domain amplitudeSILICA_TENSOR_AMPLITUDEexplains, which is what makes the declaredn2come out right on the fundamental.On an envelope grid, and on a field grid with the analytic-signal cubic, the drive is the beat-free
Re(E_a,k conj(E_a,l))/2instead, exactly asraman_drive()chooses it for the scalar response.From optmat. With parameters an optmat tensor bundle (
SilicaBundle,SilicaBundle.resolve( tensor=True)), Hellwarth’s \(a\) and \(b\) are optmat’s ownh_r(t, "a")andh_r(t, "b"), normalised so that \(\int(a+b)=1\), and the fraction is the bundle’s (0.245 for Lin and Agrawal’s shape): the same two channels, \(a + b/3\) and \(2b/3\), and the same endpoint rule, with the right-hand limits at zero delay taken from optmat. A copolarised-only bundle (Hollenbeck and Cantrell’s, the scalar default) has no \(a\) and \(b\) and is refused by name.- Attributes:
- parameterssolprop.physdata.raman_types.LinAgrawalRaman or SilicaBundle
An optmat silica bundle with a tensor shape, by default optmat’s recommended tensor bundle (
SilicaBundle.resolve(tensor=True), Lin and Agrawal’s shape, SEP 3, D11), or a caller’s own time constants, weights and Raman fraction.
- Raises:
- MaterialError
If an optmat bundle has no tensor parts.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import SilicaTensorResponse >>> response = SilicaTensorResponse() >>> response.fraction 0.245 >>> t = np.arange(20000) * 1e-16 >>> integrals = response.kernels(t).sum(axis=1) * 1e-16 / response.fraction >>> [float(round(one, 4)) for one in integrals] [1.25, 0.25]
- evaluate(field_t, chi3, *, kind, dt, thg=True)[source]#
Return the delayed polarisation on the fine time grid, in C/m^2.
The reference implementation, and therefore the oracle a compiled route is measured against. Like
RamanResponse.evaluate()it convolves on a doubled, zero-padded grid, so the transform product is the linear convolution exactly and the response’s tail cannot wrap onto the head of the drive.- Parameters:
- field_tnumpy.ndarray
The local Cartesian field on the fine time grid, shape
(n_pol, n_to)with one, two or three components: realE(t)in V/m on a field grid, complexA(t)in V/m on an envelope grid.- chi3float
The region’s absolute third-order susceptibility in m^2/V^2, non-negative. The Raman fraction is already in the kernels.
- kind{‘field’, ‘envelope’}
The temporal representation the samples belong to.
- dtfloat
Sample spacing of the fine time grid, s, positive.
- thgbool, optional
On a field grid, whether the drive keeps the
2 omegabeat –E_kE_l, which radiates the third harmonic through the later multiplication byE– or is the beat-freeRe(E_a,k conj(E_a,l))/2. Ignored on an envelope grid, which cannot carry the beat.
- Returns:
- numpy.ndarray
The polarisation in C/m^2, shape
(n_pol, n_to), real on a field grid and complex on an envelope grid.
- Raises:
- GridError
If the layout is unknown, the shape or dtype does not match it, the spacing is not positive, or samples are non-finite.
- MaterialError
If the susceptibility is negative or not finite.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import SilicaTensorResponse >>> t = (np.arange(64) - 32) * 1e-15 >>> field = np.stack([1e9 * np.exp(-((t / 5e-15) ** 2)), np.zeros(64), ... np.zeros(64)]) >>> out = SilicaTensorResponse().evaluate( ... field, 2.2e-22, kind="field", dt=1e-15 ... ) >>> out.shape, out.dtype ((3, 64), dtype('float64'))
- property fraction: float[source]#
Return the Raman fraction
f_Rthe parameters declare.- Returns:
- float
A share of the medium’s third-order susceptibility, in
(0, 1]: an optmat bundle’s ownf_R.
- hellwarth(t)[source]#
Return Hellwarth’s isotropic
aand anisotropicb, in 1/s.a + bis the copolarised response \(h_R\) and integrates to one; both are zero at and below zero delay. From the declared parameters,a = w_a h_aandb = w_b h_b + w_ab h_a; from an optmat bundle, optmat’sh_r(t, "a")andh_r(t, "b").- Parameters:
- tnumpy.ndarray
Delays in s, shape
(n_to,), finite and real.
- Returns:
- numpy.ndarray
Shape
(2, n_to)in 1/s: row zeroa, row oneb.
- Raises:
- GridError
If the delays are not a finite real one-dimensional array.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import SilicaTensorResponse >>> parts = SilicaTensorResponse().hellwarth(np.arange(20000) * 1e-16) >>> float(round(parts.sum() * 1e-16, 3)) 1.0
Return
1 - f_R, the share the instantaneous Kerr term keeps.- Returns:
- float
A dimensionless share in
[0, 1).
- kernels(t)[source]#
Return the trace and rank-two seam kernels, in 1/s.
(3/2) f_R (a + b/3)and(3/2) f_R (2 b/3)of the class docstring, which integrate toSILICA_TENSOR_AMPLITUDEtimesf_RtimesSILICA_TENSOR_INTEGRALS. Both the amplitude factor and the Raman fraction are included here and nowhere else, so that a compiled record multiplies bychi3alone and the fraction is applied exactly once; this is also the one place the sampled kernel is built, so the endpoint rule below is seen by every caller.The endpoint quadrature is the trapezoid, and the sample at zero delay carries half the kernel’s right-hand limit there.
h_b(0^+) = 2/\tau_bis not zero, so a causal kernel sampled onarange(n) dtand integrated by the rectangle-rule transform product missesdt h(0^+)/2and converges only to first order: sampling the jump at its midpoint – the mean of the two one-sided limits, one of which is zero – turns that sum into the trapezoid rule and restores second order. The half-weighted sample multipliesQ(t)itself, so it costs no causality. The late lobe ofh_b, wheret > 2\tau_b, is genuinely negative and is left unclipped: it is part of the response, not a gain.- Parameters:
- tnumpy.ndarray
Delays in s, shape
(n_to,), finite and real. The endpoint rule assumes a uniform axis starting at zero, which is what both callers pass.
- Returns:
- numpy.ndarray
Shape
(2, n_to)in 1/s: row zero the trace channel, row one the traceless rank-two channel.
- Raises:
- GridError
If the delays are not a finite real one-dimensional array.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import SilicaTensorResponse >>> kernels = SilicaTensorResponse().kernels(np.arange(8) * 1e-15) >>> kernels.shape (2, 8)
- parameters: LinAgrawalRaman | SilicaBundle#
- shapes(t)[source]#
Return the two normalised response functions
h_aandh_b.Each integrates to one over the positive axis; both are zero at and below zero delay, which is what makes the later convolution causal.
- Parameters:
- tnumpy.ndarray
Delays in s, shape
(n_to,), finite and real.
- Returns:
- numpy.ndarray
Shape
(2, n_to)in 1/s: row zeroh_a, row oneh_b.
- Raises:
- GridError
If the delays are not a finite real one-dimensional array.
- MaterialError
For an optmat bundle, which gives Hellwarth’s
aandbrather thanh_aandh_b(hellwarth()).
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import SilicaTensorResponse >>> from solprop.physdata import LinAgrawalRaman >>> declared = LinAgrawalRaman( ... tau1=12.2e-15, ... tau2=32.0e-15, ... tau_b=96.0e-15, ... isotropic_weight=0.75, ... boson_weight=0.21, ... anisotropic_weight=0.04, ... fraction=0.245, ... citation="Lin and Agrawal, Opt. Lett. 31, 3086 (2006)", ... ) >>> response = SilicaTensorResponse(declared) >>> shapes = response.shapes(np.array([-1e-15, 0.0, 1e-14])) >>> [float(round(one, 4)) for one in shapes[:, 0]] [0.0, 0.0]
- tensors()[source]#
Return the two dimensionless rank-four channel tensors.
D[trace]_ijkl = d_ij d_klandD[rank]_ijkl = (3 (d_ik d_jl + d_il d_jk) - 2 d_ij d_kl)/4, contracted asP_i = ... D_ijkl E_j H_klwithHthe convolved pair history. Both are symmetric in their last two indices, because a pair history is, and that symmetry is what makes the six stored histories enough.- Returns:
- numpy.ndarray
Shape
(2, 3, 3, 3, 3), dimensionless.
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import SilicaTensorResponse >>> tensors = SilicaTensorResponse().tensors() >>> [float(one[0, 0, 0, 0]) for one in tensors] [1.0, 1.0]
- property traits: ResponseTraits[source]#
Return the declared traits of this response.
- Returns:
- ResponseTraits
A convolution on a field grid, coupling Cartesian components, with no density path at all: a glass has none, and the magnitude comes from the region’s own
chi3.
Examples
>>> from solprop.nonlinear.raman import SilicaTensorResponse >>> SilicaTensorResponse().traits.polarisation 'tensor'
- solprop.nonlinear.raman.bundle_response(bundle, *, amplitude=1.0, fraction=None, thg=True, tensor=False)[source]#
Build fused silica’s Raman response from an optmat bundle.
The scalar response is one
scalar_onlySampledShapeof the bundle’s copolarised shape. The tensor response is the bundle’s isotropic channela + b/3and rank-two channel2 b/3as twoRamanTensorTermdeclarations, which the seam weights(1, 0)and(1/4, 3/4).- Parameters:
- bundleSilicaBundle
The optmat silica bundle; a tensor response needs one with tensor parts.
- amplitudefloat, optional
The value
integral h dtof the copolarised response, m^5/V^2 per unit density, before fraction. Default 1.0.- fractionfloat or None, optional
The Raman fraction in
(0, 1]the response carries, orNone.- thgbool, optional
Whether a field grid keeps the third harmonic of the drive. Default
True.- tensorbool, optional
Build the tensor declaration, for a vector run. Default
False.
- Returns:
- RamanResponse
One scalar-only term, or an isotropic and a rank-two term.
- Raises:
- MaterialError
If a tensor response is asked of a copolarised-only bundle, naming it, or the fraction is outside
(0, 1].
Examples
>>> from solprop.nonlinear.raman import bundle_response >>> from solprop.physdata.glasses import SilicaBundle >>> bundle_response(SilicaBundle.resolve()).tensor_capable False >>> response = bundle_response(SilicaBundle.resolve(tensor=True), tensor=True) >>> [term.contraction for term in response.tensor_terms] ['isotropic', 'rank2']
- solprop.nonlinear.raman.normalised_damped_oscillator(amplitude, frequency, dephasing_time)[source]#
Build a damped oscillator whose response integrates to a given value.
The integral of
K sin(Omega t) exp(-t / tau2)over the positive axis isK Omega / (Omega^2 + 1/tau2^2), so scalingKby the reciprocal of that factor makes the integral exactlyamplitude. Useful wherever the Raman strength is known as a fraction of the total nonlinearity rather than from molecular constants, which is how a fibre GNLSE states it.A module-level function rather than a classmethod, for the reason given in
solprop.physdata.raman_types.linewidth_from_dephasing_time().- Parameters:
- amplitudefloat
The value
integral h dtis to take, m^5/V^2 per unit density.- frequencyfloat
Angular frequency of the transition, rad/s, positive.
- dephasing_timefloat
Density-independent coherence time
tau2in s, positive.
- Returns:
- DampedOscillator
The line, with a density-independent dephasing.
- Raises:
- MaterialError
If the frequency or the dephasing time is not positive.
Examples
>>> import numpy as np >>> from scipy.integrate import quad >>> from solprop.nonlinear.raman import normalised_damped_oscillator >>> line = normalised_damped_oscillator(1.0, 2 * np.pi * 100e12, 100e-15) >>> integral = quad( ... lambda t: float(line.undamped(np.array([t]))[0]) ... * float(line.dephasing.damping(np.array([t]), 1.0)[0]), ... 0.0, ... 2e-12, ... limit=400, ... )[0] >>> bool(abs(integral - 1.0) < 1e-8) True
- solprop.nonlinear.raman.raman_drive(field_t, *, kind, thg)[source]#
Return the real quantity the Raman response is driven by.
The Raman coherence is driven by the intensity of the field, and which real quadratic form that is depends on the grid, exactly as the Kerr cubic does (
docs/guide/media/nonlinear.md):grid
thgdrive
field
True
E^2, which carries the2 omegabeat and so radiates the third harmonic through the later multiplication byEfield
False
|E_a|^2 / 2, the cycle-averaged intensity, with the beat removedenvelope
–
|A|^2 / 2, the same quantity written on the envelopeThe factor of one half is the cycle average of
cos^2:E^2and|E_a|^2 / 2agree in their slowly varying part, so the two field-grid forms drive the coherence identically and differ only in the harmonic.- Parameters:
- field_tnumpy.ndarray
The field on the fine time grid, shape
(1, n_to): realE(t)in V/m on a field grid, complexA(t)in V/m on an envelope grid.- kind{‘field’, ‘envelope’}
The spectral layout the samples belong to.
- thgbool
Whether the third harmonic is kept. Ignored on an envelope grid, which cannot carry it in a quadratic drive.
- Returns:
- numpy.ndarray
The drive in V^2/m^2, real, shape
(n_to,).
Examples
>>> import numpy as np >>> from solprop.nonlinear.raman import raman_drive >>> raman_drive(np.array([[3.0, -2.0]]), kind="field", thg=True).tolist() [9.0, 4.0] >>> raman_drive(np.array([[2.0 + 0j]]), kind="envelope", thg=True).tolist() [2.0]
- solprop.nonlinear.raman.response_for(material, options=None, *, amplitude=1.0, fraction=None, thg=True, selection=None, density=None)[source]#
Build the Raman response of a named material.
A built-in gas’s response is optmat’s (SEP 3), absolute, so its
fractionmust be leftNone; a caller’s species with its own molecular constants (MolecularRaman) gives an absolute response built from them; fused silica gives optmat’s bundle shape, normalised, whose magnitude the caller supplies, either asamplitudeor, downstream, asfractiontimes a susceptibility.No material selects a normalised damped oscillator; build one with
normalised_damped_oscillator()if you want it.- Parameters:
- materialGas, str or Species
A built-in gas, a caller’s species with Raman constants, or one of
SILICA_NAMESfor fused silica.- optionsMolecularOptions or None, optional
Which parts of a molecular spectrum to build, and at what temperature. Ignored for silica.
Noneand the default options include every available term atsolprop.constants.ROOM_TEMPERATURE; a term set toTrueis refused by species name when it has no model.- amplitudefloat, optional
For silica only: the value
integral h dtis to take, m^5/V^2 per unit density. Default 1.0, a pure shape.- fractionfloat or None, optional
The Raman fraction
f_Rin(0, 1], orNone. Refused for a molecular gas, whose models are absolute and would be scaled twice.- thgbool, optional
Whether a field grid keeps the third harmonic of the drive. Default
True.- selectionMaterialSelection or None, optional
NoneisMaterialSelection(). A built-in gas’s response is optmat’s under it, dispersed to itscarrier_wavelength, which must then be set (solprop.nonlinear.optmat_raman.gas_raman()); fused silica’s is the bundle itsramannames. Refused beside a caller’s species.- densityfloat or None, optional
For a built-in gas, the constant number density in m^-3 of the fill: checked against the response’s validated range, and the density at which H2’s shifted lines are built.
- Returns:
- RamanResponse
The response: for silica one term; for a built-in gas an
OptmatRamanResponsewith one term per damping group and tensor family; for a caller’s species one term per part of its spectrum.
- Raises:
- MaterialError
If the material is unknown, has no Raman data, has no part left after the options, is a gas given a
fraction, or is a built-in gas without a carrier.
Examples
>>> from solprop.nonlinear.raman import response_for >>> from solprop.physdata import Gas, MaterialSelection >>> selection = MaterialSelection(carrier_wavelength=800e-9) >>> type(response_for(Gas.N2, selection=selection)).__name__ 'OptmatRamanResponse' >>> len(response_for("SiO2", fraction=0.18).models) 1
- solprop.nonlinear.raman.rotational_nonrigid(parameters, temperature=293.15, *, min_j=0, max_j=50)[source]#
Build the rotational Raman spectrum of a linear molecule.
The ladder is truncated wherever the non-rigid-rotor energy stops increasing with
J: beyond that point the expansionB J(J+1) - D (J(J+1))^2has left its range of validity and the “levels” it predicts are an artefact. For H2, whose centrifugal distortion is large, that happens atJ = 24.Populations are Boltzmann with the nuclear-spin statistical weights, normalised over the retained levels. The line weight of the
J -> J + 2pair is\[K \frac{(J+1)(J+2)}{2J+3} \left(\frac{p_{J+2}}{2J+5} - \frac{p_J}{2J+1}\right), \qquad K = -\frac{(4\pi\varepsilon_0)^2}{\varepsilon_0} \frac{2}{15}\frac{\Delta\alpha^2}{\hbar},\]with
p_Jthe fractional populations.Kis negative and so is the bracket, because the population per magnetic sublevel falls withJ, so the lines come out positive: the same sign as a vibration, and the sign that amplifies the Stokes side.- Parameters:
- parametersRotationalRaman
The molecule’s rotational constants, the caller’s own.
- temperaturefloat, optional
Gas temperature in K, positive. Default
solprop.constants.ROOM_TEMPERATURE.- min_jint, optional
Lowest rotational quantum number to include, non-negative. Default 0.
- max_jint, optional
Highest rotational quantum number to consider before truncation. Default 50.
- Returns:
- RotationalLines
The retained levels, their populations and the resulting lines.
- Raises:
- MaterialError
If the temperature is not positive, the
Jrange is inverted or shorter than three levels, or the truncation leaves too few levels to form a pair.
References
Laser Photonics Rev. 16, 2100426 (2022), doi:10.1002/lpor.202100426, the SI form of the model of Wahlstrand et al., Phys. Rev. A 92, 063828 (2015), doi:10.1103/PhysRevA.92.063828.
Examples
>>> from solprop.nonlinear.raman import rotational_nonrigid >>> from solprop.physdata import Linewidth, RotationalRaman >>> hydrogen = RotationalRaman( ... b=5890.0, ... d=5.0, ... degeneracy_odd=3, ... degeneracy_even=1, ... polarisability_anisotropy=3e-31, ... linewidth=Linewidth(inverse_density=6.15e6, density=114e6), ... citation="a caller's H2 constants", ... ) >>> lines = rotational_nonrigid(hydrogen, 293.15) >>> int(lines.quantum_numbers[-1]) 24 >>> lines.n_lines 23
- solprop.nonlinear.raman.undamped_dephasing()[source]#
Return the dephasing of a coherence that does not decay with density.
The right choice for a model that carries its own damping, such as the intermediate-broadening line set of a glass, whose Lorentzian widths are the dephasing and do not depend on how much glass there is.
- Returns:
- Dephasing
A dephasing whose factor is one at every density and every delay.
Examples
>>> from solprop.nonlinear.raman import undamped_dephasing >>> undamped_dephasing().decay_rate(1e25) 0.0
- solprop.nonlinear.raman.vibrational(parameters)[source]#
Build the Raman response of a molecular vibration.
A vibrational mode is one damped oscillator whose strength comes from the volume polarisability derivative along a displacement coordinate,
\[K = \frac{(4\pi\varepsilon_0\, d\alpha/dQ)^2} {2\mu\Omega_v\varepsilon_0},\]the
(4 pi eps0)converting the tabulated volume polarisability derivative to an SI one and the finaleps0being the one this package’shcarries outside the integral (see the module docstring). The induced-dipole energy-alpha_SI(Q) E_real^2 / 2gives forcealpha_SI'(Q) E_real^2 / 2. Its retarded displacement response gives the denominator two above. The cycle average is already inraman_drive(); applying another half here would count it twice, which is why the denominator is two and not four. For a diatomic bond coordinate use its reduced mass; do not insert a mass-weighted normal-coordinate derivative while retaining that same mass.- Parameters:
- parametersVibrationalRaman
The mode’s constants, the caller’s own (a built-in gas’s response is optmat’s,
solprop.nonlinear.optmat_raman.gas_raman()).
- Returns:
- DampedOscillator
The vibrational line, with the mode’s own density-dependent dephasing.
References
S.-F. Gao, Y.-Y. Wang, F. Belli, C. Brahms, P. Wang and J. C. Travers, “From Raman frequency combs to supercontinuum generation in nitrogen-filled hollow-core anti-resonant fiber”, Laser Photonics Rev. 16, 2100426 (2022), doi:10.1002/lpor.202100426, and Wahlstrand et al., Phys. Rev. A 92, 063828 (2015), doi:10.1103/PhysRevA.92.063828, are the inherited model lineage. The prefactor here is independently derived from the induced-dipole Hamiltonian under this module’s field convention, rather than copied from that lineage. N2 bond-coordinate data are from Murphy, Holzer and Bernstein, Appl. Spectrosc. 23, 211 (1969), Table I and Eq. (4), doi:10.1366/000370269774380824. The prefactor’s common amplitude is verified by an independent quantum-oscillator impulse calculation; each material’s tabulated derivative is not independently recalibrated, and the transfer of N2’s derivative from 435.8 nm to the near infrared and the omission of the anisotropic vibrational response remain assumptions.
Examples
>>> from solprop.constants import ATOMIC_MASS >>> from solprop.nonlinear.raman import vibrational >>> from solprop.physdata import Linewidth, VibrationalRaman >>> mode = VibrationalRaman( ... frequency=7.826769e14, ... polarisability_derivative=1.3e-20, ... reduced_mass=1.00784 * ATOMIC_MASS / 2.0, ... linewidth=Linewidth(inverse_density=309e6, density=52.2e6), ... citation="a caller's H2 constants, Phys. Rev. A 92, 063828 (2015)", ... ) >>> line = vibrational(mode) >>> line.frequency == mode.frequency True >>> f"{line.amplitude:.6e}" '1.803981e-37'