solprop.nonlinear.responses#
Nonlinear response definitions and their declared traits.
A response says how a medium answers a field: the nonlinear polarisation P(t) it
produces from the field E(t) at a point, and the declared traits the translation
layer needs in order to compile a set of responses into one fused time-domain pass.
This module holds the Kerr and plasma responses, SEP 6’s frequency-dependent electronic
response (DispersiveKerrResponse), and the trait vocabulary the delayed Raman
response of solprop.nonlinear.raman extends.
The evaluate method here is the NumPy reference: the oracle every backend kernel
is tested against, written for clarity rather than for speed
(docs/dev/architecture.md section 3). Units, signs and the analytic-signal
convention follow docs/dev/conventions.md sections 2 and 4, and the derivation of
every numerical factor is in docs/guide/media/nonlinear.md.
Module Attributes
How a response is evaluated: sample by sample, with memory along t, as a convolution, or after the forward transform ( |
|
How a response treats the polarisation axis. |
|
SEP 6's two dispersive evaluators: the symmetric factorisation of the Kerr diagonal, and the gas dispersion polynomial itself. |
|
Which quartets a dispersive cubic keeps: all of them, or the number-conserving ones with two positive and two negative signed legs (SEP 6, Phase 0, decision Q4). |
|
Largest polarisation axis a response accepts: |
Functions
|
Return SEP 6's dispersive cubic of a field, per unit |
|
Return the symmetric evaluator's filter of a gas mixture and its coefficient. |
|
Return the carrier factor an envelope-grid third-harmonic term needs. |
Classes
|
The frequency-dependent electronic third-order response of a gas (SEP 6). |
|
The spectral multipliers of one species' dispersive cubic on a fine grid. |
|
The instantaneous third-order (Kerr) polarisation of an isotropic medium. |
|
The polarisation of the free electrons a strong field tears from a gas. |
|
What the translation layer must know about a response to compile it. |
- class solprop.nonlinear.responses.DispersiveKerrResponse(model, evaluator, thg=True, species=None)[source]#
Bases:
objectThe frequency-dependent electronic third-order response of a gas (SEP 6).
One broadband polarisation from optmat’s whole hyperpolarizability model of the gas,
P = eps0 gamma3_0 rho x (a filtered cubic of the field), the kernel evaluated at the absolute frequencies of every quartet the cubic couples. No SPM, XPM, FWM or THG event is identified: the four-frequency rule assigns each its coefficient.Two evaluators (
docs/dev/seps/archive/sep-0006-dispersive-nonlinearity.md, sections 4.4-4.5 and the Phase 0 report):"polynomial"The gas dispersion polynomial itself, \(\gamma_0[1 + a S_2 + b S_2^2 + b' S_4]\), by its grouped FFT form: exact evaluation of the fit on the resolved grid.
"symmetric"The symmetric factorisation of the Kerr diagonal, \(\gamma_0\prod_j g(\omega_j)\) with \(g = [\gamma_K(\tilde\nu)/\gamma_0]^{1/4}\): exact on the Kerr diagonal and to second order elsewhere, one filtered product.
Every leg is held at the gas’s leg bound \(\nu_c\) (section 4.10): the weight \(\tilde d = \min((\omega/2\pi c)^2, \nu_c^2)\), real and even, so the lossless work identity holds and no frequency a field occupies is refused.
thgselects the sector (Phase 0, decision Q4):Trueis the full cubic, all quartets, harmonic and inverse harmonic exchange included;Falseis the number-conserving sector, the quartets with two positive and two negative signed legs, with \(\Pi_+\) on absolute frequency. Below an octave the latter equals the constant cubic’sthg=Falserule; beyond it, unlike that rule, it does no net work. Two polarisation components contract through the isotropic Kleinman tensor, the scalar kernel’s named vector approximation (decision Q3).- Attributes:
- modelsolprop.physdata.nonlinear_data.DispersiveKerrModel
optmat’s model of the gas in SI, with its leg bound and band edges.
- evaluator{‘symmetric’, ‘polynomial’}
Which evaluator.
- thgbool, optional
Truefor the full cubic,Falsefor the number-conserving sector. DefaultTrue.- speciesstr or None, optional
Component label in a mixture;
Noneretains the single-medium spelling.
- Raises:
- MaterialError
If the evaluator is not one of the two.
Examples
>>> from solprop.nonlinear.responses import DispersiveKerrResponse >>> from solprop.physdata.nonlinear_data import OptmatKerr >>> model = OptmatKerr("Ar").dispersive_model() >>> response = DispersiveKerrResponse(model, "polynomial", thg=False) >>> response.sector, response.traits.domain ('conserving', 'convolution')
- property coefficient: float[source]#
Return
eps0 gamma3_0, the static coefficient per unit density.- Returns:
- float
In C m^2/V^3 (m^5/V^2 times eps0), multiplying the number density.
- evaluate(field_t, density, *, kind, omega, carrier=None, window=None)[source]#
Return the polarisation on the fine time grid: the reference evaluation.
The oracle every backend is tested against, itself held to the triplet sum of SEP 6’s Phase 0 (
tests/dispersive_kerr_oracle.py). It allocates and is not written for speed.- Parameters:
- field_tnumpy.ndarray
The field on the fine time grid, shape
(n_pol, n_to): realE(t)in V/m on a field grid, the complex envelopeA(t)in V/m on an envelope grid. One, two or three components: three is the general modal route’s full vector field, contracted through the same isotropic tensor.- densityfloat
Number density in m^-3, non-negative.
- kind{‘field’, ‘envelope’}
The spectral layout the samples belong to.
- omeganumpy.ndarray
The absolute angular frequency of every bin of the fine transform in rad/s: shape
(n_to // 2 + 1,)on a field grid,(n_to,)in FFT order on an envelope grid (TimeGrid.omega_fine).- carriernumpy.ndarray or None, optional
exp(2 i omega0 t)on the fine grid, shape(n_to,); required whenkind='envelope'andthgis true, rejected otherwise (thg_carrier()).- windownumpy.ndarray or None, optional
The temporal collar in
[0, 1], shape(n_to,): the grid’st_window_fine, which a propagation applies. It multiplies each product in time before the output multipliers,G[w (G E)^3](dispersive_cubic());None, the default, is a unit collar.
- Returns:
- numpy.ndarray
The polarisation in C/m^2, shape
(n_pol, n_to): real on a field grid, the envelope of the analytic polarisation on an envelope grid.
- Raises:
- GridError
If the layout is unknown, the arrays do not match it, the frequencies do not match the transform, the carrier is missing, unwanted or the wrong length, or the collar’s length is not the field’s.
- model: DispersiveKerrModel#
- property sector: Literal['full', 'conserving'][source]#
Return which quartets the response keeps.
- Returns:
- {‘full’, ‘conserving’}
"full"withthg,"conserving"without.
- property traits: ResponseTraits[source]#
Return the declared traits of the response.
- Returns:
- ResponseTraits
A convolution in time (spectral multipliers on the inputs and the output), both grids, vector-isotropic, consuming the density path.
- weights(omega, *, kind)[source]#
Return the multipliers on a fine grid’s spectral layout.
- Parameters:
- omeganumpy.ndarray
The absolute angular frequency of every bin, rad/s:
k domegaon a field grid’s one-sided layout,omega0 + vin FFT order on an envelope grid.- kind{‘field’, ‘envelope’}
The layout.
- Returns:
- DispersiveWeights
The response’s multipliers there, read-only.
- class solprop.nonlinear.responses.DispersiveWeights(evaluator, positive, filter, output=None, products=(0.0, 0.0))[source]#
Bases:
objectThe spectral multipliers of one species’ dispersive cubic on a fine grid.
Every multiplier is a real, even function of the absolute angular frequency of each bin of the fine transform’s spectrum: the one-sided
rfftlayout,(n_to // 2 + 1,), on a field grid, and the full FFT-order layout,(n_to,), on an envelope grid. The compiled bundle of the translation layer carries the same arrays, one row per species (solprop.translate.responses.DispersiveKerrTerms).- Attributes:
- evaluator{‘symmetric’, ‘polynomial’}
Which evaluator the arrays belong to.
- positivenumpy.ndarray
Pi_+: the bins of strictly positive absolute frequency, bool. On a field grid the DC and the Nyquist bins are excluded.- filternumpy.ndarray
The input multiplier, float64: the symmetric factor
g(dimensionless), or the polynomial’s bounded leg weightdin m^-2.- outputnumpy.ndarray or None
The polynomial’s two output multipliers, float64, shape
(2, n_omega_fine):1 + a d + (b + b') d^2(dimensionless) and3 a + 6 b d(m^2).Nonefor the symmetric evaluator, whose output multiplier isfilteragain.- productstuple of float
The polynomial’s constant weights of
E^2 D^2 EandE (D E)^2,3 (b + b')and6 bin m^4;(0, 0)for the symmetric evaluator.
- filter: Float[ndarray, 'n_omega_fine']#
- positive: Bool[ndarray, 'n_omega_fine']#
- solprop.nonlinear.responses.Domain#
How a response is evaluated: sample by sample, with memory along t, as a convolution, or after the forward transform (
docs/dev/architecture.mdsection 6.6).alias of
Literal[‘pointwise’, ‘memory’, ‘convolution’, ‘spectral’]
- solprop.nonlinear.responses.Evaluator#
SEP 6’s two dispersive evaluators: the symmetric factorisation of the Kerr diagonal, and the gas dispersion polynomial itself.
alias of
Literal[‘symmetric’, ‘polynomial’]
- class solprop.nonlinear.responses.KerrResponse(chi3, thg=True, species=None)[source]#
Bases:
objectThe instantaneous third-order (Kerr) polarisation of an isotropic medium.
P = eps0 chi3 rho x (a cubic form of the field), on either grid, for one or two polarisation components. The cubic form differs between the grids because a field grid resolves the carrier and an envelope grid does not; the four cases and their numerical factors are derived indocs/guide/media/nonlinear.md.chi3is the susceptibility per unit of the density argument, so a gas passes the per-particle hyperpolarisability with its number density in m^-3 and a fixed medium passes chi3 itself with a density of 1.0. This is the convention the kernel contract’sdensityprofile assumes.- Attributes:
- chi3float
Third-order susceptibility per unit density, m^2/V^2 when the density is dimensionless, m^5/V^2 when it is a number density in m^-3.
- thgbool, optional
Whether the response radiates the third harmonic. On a field grid
Trueis the full cubic andFalseremoves the harmonic; on an envelope gridTrueadds the harmonic term, which needs a grid built withthg=Trueand the carrier fromthg_carrier(). DefaultTrue.- speciesstr or None, optional
Component label in a mixture;
Noneretains the single-medium spelling.
Examples
>>> import numpy as np >>> from solprop.constants import EPS0 >>> from solprop.nonlinear.responses import KerrResponse >>> kerr = KerrResponse(chi3=1.0, thg=True) >>> kerr.traits.domain 'pointwise' >>> field = np.array([[1.0, 2.0]]) >>> polarisation = kerr.evaluate(field, 1.0, kind="field") >>> bool(np.array_equal(polarisation, EPS0 * np.array([[1.0, 8.0]]))) True
- evaluate(field_t, density, *, kind, carrier=None)[source]#
Return the Kerr polarisation on the fine time grid.
The reference implementation, and therefore the oracle for every backend. It allocates and is not written for speed.
- Parameters:
- field_tnumpy.ndarray
The field on the fine time grid, shape
(n_pol, n_to): realE(t)in V/m on a field grid, complexA(t)in V/m on an envelope grid.- densityfloat
Density multiplying
chi3: a number density in m^-3 for a gas, 1.0 for a medium whosechi3is already absolute. Non-negative.- kind{‘field’, ‘envelope’}
The spectral layout the samples belong to.
- carriernumpy.ndarray or None, optional
exp(2 i omega0 t)on the fine grid, shape(n_to,); required whenkind='envelope'andthgis true, rejected otherwise. Build it withthg_carrier().
- 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 array shape or dtype does not match it, or the carrier is missing, unwanted or the wrong length.
Examples
>>> import numpy as np >>> from solprop.constants import EPS0 >>> from solprop.nonlinear.responses import KerrResponse >>> envelope = np.array([[2.0 + 0.0j]]) >>> out = KerrResponse(chi3=1.0, thg=False).evaluate( ... envelope, 1.0, kind="envelope" ... ) >>> bool(np.allclose(out, 0.75 * EPS0 * 8.0)) True
- property traits: ResponseTraits[source]#
Return the declared traits of the Kerr response.
- Returns:
- ResponseTraits
Pointwise, both grids, vector-isotropic, consuming the density path.
Examples
>>> from solprop.nonlinear.responses import KerrResponse >>> KerrResponse(1.0).traits.z_profiles ('density',)
- solprop.nonlinear.responses.MAX_N_POL = 2#
Largest polarisation axis a response accepts:
(x, y)(conventions.md5).
- class solprop.nonlinear.responses.PlasmaResponse(gas, rate, *, preionisation=0.0, species=None, integration='band')[source]#
Bases:
objectThe polarisation of the free electrons a strong field tears from a gas.
Two currents, both per unit density and both integrated along the time axis (Geissler et al. 1999). Write
f(t)for the fraction of atoms ionised by timetandWfor the ionisation rate at the instantaneous field:the free-electron current
J_free = (e^2/m_e) int f E dt, the current of electrons already freed and being accelerated by the field. It is what lowers the refractive index and blue-shifts the pulse;the ionisation loss
J_loss = I_p W (1-f) E / |E|^2, added pointwise. Its form is chosen so thatE . J_loss = I_p W (1-f) = I_p df/dtexactly: the field pays one ionisation potential for every electron it frees.
The polarisation is
rhotimes the integral of their sum, soevaluatedoes three cumulative integrals in all. It is a memory response: the value at one sample depends on every earlier sample. Under the plain trapezoid it depends on none of the later ones; under the band rule (SEP 18) each integral readsKsamples ahead, so the polarisation at one sample depends on samples up to3Klater, a band-limited approximation to the causal response whose precursor is confined to those samples.Production forms the held-current source directly in frequency (SEP 27), using only the population and free-current integrals. This does not change the physical polarisation returned by
evaluate.integrationselects the three physical-response integrals.'band'filters each integrand with symmetric taps designed for the grid, so every integral is exact to a certified1e-5across the active band. The plain trapezoid is short by1 - (theta/2) cot(theta/2)per integral attheta = omega dt, 11 % at the top of the default active band.'trapezoid'is that plain rule, bit for bit, and is what the frozen witness comparisons use. The taps belong to the grid, not to the response: a propagation designs them in translation, and a direct call toevaluate()orionised_fraction()under the band rule is passed them asintegral(solprop.translate.plasma_integration.design_running_integral()).The ionised fraction is
f = 1 - (1 - f_0) exp(-int W dt), with the pre-ionised fraction inside the exponential. Adding it outside,f_0 + 1 - exp(-int W dt), would exceed one, make1 - fnegative and break the energy bookkeeping above, becausedf/dtwould then no longer beW (1 - f).preionisationis a number or a path alongz: the fraction ionised before the pulse arrives at each position. The trait declares it as a path because that is what the translation layer supplies – a value per plane – and the reference evaluation sees one plane at a time. Where the path varies, the oracle for a plane isdataclasses.replace()with that plane’s value, which rebuilds and revalidates the response; the profile itself is never resolved here, andevaluate()andionised_fraction()refuse a response that still carries one.- Attributes:
- gasGas or str or Species
The species, for its ionisation potential. Beside a rate that carries its own energy per event (an
OptmatRate), the species is stored with that energy as its potential (bind_rate_energy(), SEP 3, D8).- rateEvaluableRate
The ionisation rate in s^-1 as a function of field amplitude in V/m: optmat’s
solprop.nonlinear.optmat_rates.OptmatRate, a caller’s rate, or thesolprop.translate.ionisation.RateTablea propagation actually uses. It must be exactly zero at zero field, which is what makes the loss term finite.- preionisationfloat or ZProfile, optional
Fraction already ionised before the pulse, in
[0, 1]: a number, or a path along z in m giving that fraction at each position. A crude model: it says nothing about where those electrons came from or what they were doing.- speciesstr or None, optional
Component label, distinct from the chemical species when populations split.
- integration{‘band’, ‘trapezoid’}, optional
The running-integral rule of the column (SEP 18): the band rule, the default, or the plain cumulative trapezoid.
- Raises:
- MaterialError
If the gas is unknown or has no ionisation potential, the pre-ionised fraction is neither a number nor a path or leaves
[0, 1], the rate is not zero at zero field, it carries an energy per event for another species, orintegrationis not a known rule.
Examples
>>> import numpy as np >>> from solprop.nonlinear.optmat_rates import OptmatRate >>> from solprop.nonlinear.responses import PlasmaResponse >>> plasma = PlasmaResponse("Ar", OptmatRate("Ar", 800e-9)) >>> plasma.traits.domain 'memory'
Evaluated directly, the band rule is handed its grid’s design:
>>> from solprop.grid import FieldGrid >>> from solprop.translate.plasma_integration import design_running_integral >>> grid = FieldGrid.from_limits(1.0, 800e-9, (200e-9, 2e-6), 40e-15) >>> t = (np.arange(grid.n_to) - grid.n_to // 2) * grid.dt_fine >>> field = 4e10 * np.exp(-((t / 8e-15) ** 2)) >>> rule = design_running_integral(grid, "band") >>> fraction = plasma.ionised_fraction( ... field[None, :], dt=grid.dt_fine, kind="field", integral=rule ... ) >>> f"{fraction[-1]:.4f}" '0.3209'
- evaluate(field_t, density, *, dt, kind, integral=None)[source]#
Return the plasma polarisation on the fine time grid.
Return the physical, ungated polarisation, including its post-pulse drift. This reference integrates the population, free current and polarisation in three separate passes. Field-grid propagation instead holds the same total current at the temporal boundary: it assembles
DFT[g J]/(i omega) - DFT[g' J]/(i omega)**2on active bins (SEP 27). That assembly omits the final running integral; it does not change this physical-response oracle.- Parameters:
- field_tnumpy.ndarray
The real field on the fine time grid, V/m, shape
(n_pol, n_to).- densityfloat
Number density of the gas in m^-3, non-negative. The rate and the fraction are per atom; this is what turns them into a polarisation.
- dtfloat
Sample spacing of the fine time grid, s, positive.
- kind{‘field’}
The spectral layout; only a field grid is defined.
- integralRunningIntegral or None, optional
The grid’s running-integral rule, required under
integration='band'.
- Returns:
- numpy.ndarray
The polarisation in C/m^2, shape
(n_pol, n_to), real.
- Raises:
- GridError
If the layout is not a field grid, the array shape or dtype does not match it, the time step is not positive, the density is negative, or the rate returns the wrong shape or a value that is not finite and non-negative; or if the integration rule is missing, is another rule than the response’s, or was designed for another
dt.- MaterialError
If the preionisation is a path along z rather than one plane’s value.
Examples
>>> import numpy as np >>> from solprop.nonlinear.optmat_rates import OptmatRate >>> from solprop.nonlinear.responses import PlasmaResponse >>> t = np.linspace(-2e-14, 2e-14, 257) >>> dt = float(t[1] - t[0]) >>> field = 4e10 * np.exp(-((t / 8e-15) ** 2)) * np.cos(2.35e15 * t) >>> plasma = PlasmaResponse( ... "Ar", OptmatRate("Ar", 800e-9), integration="trapezoid" ... ) >>> out = plasma.evaluate(field[None, :], 2.5e25, dt=dt, kind="field") >>> f"{out[0, -1]:.4e}" '8.9313e-07'
- ionised_fraction(field_t, *, dt, kind, integral=None)[source]#
Return the fraction of atoms ionised, sample by sample.
Multiply by the density for the electron density, which is what a run reports as a statistic. The rate is taken on the magnitude of the field, so an elliptically polarised pulse ionises according to how hard it pulls, not according to one component.
- Parameters:
- field_tnumpy.ndarray
The real field on the fine time grid, V/m, shape
(n_pol, n_to). It is annotated as any number and checked at run time, as inKerrResponse.evaluate(), so that the guard is the authority on the dtype rather than the test session’s type checker.- dtfloat
Sample spacing of the fine time grid, s, positive.
- kind{‘field’}
The spectral layout; only a field grid is defined.
- integralRunningIntegral or None, optional
The grid’s running-integral rule, required under
integration='band'.
- Returns:
- numpy.ndarray
Fraction ionised, in
[preionisation, 1], shape(n_to,), non-decreasing in time (under the band rule, to within the filter’s undershoot).
- Raises:
- GridError
If the layout is not a field grid, the array shape or dtype does not match it, the time step is not positive, or the rate returns the wrong shape or a value that is not finite and non-negative; or if the integration rule is missing, is another rule than the response’s, or was designed for another
dt.- MaterialError
If the preionisation is a path along z rather than one plane’s value.
Examples
>>> import numpy as np >>> from solprop.nonlinear.optmat_rates import OptmatRate >>> from solprop.nonlinear.responses import PlasmaResponse >>> plasma = PlasmaResponse( ... "Ar", OptmatRate("Ar", 800e-9), preionisation=0.25, ... integration="trapezoid", ... ) >>> field = np.full((1, 8), 1e6) >>> plasma.ionised_fraction(field, dt=1e-16, kind="field").tolist() [0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25]
- property potential: float[source]#
Return the ionisation potential without inventing custom species data.
- Returns:
- float
First ionisation potential in J.
- Raises:
- MaterialError
If the named custom species has no ionisation data.
- rate: EvaluableRate#
- property traits: ResponseTraits[source]#
Return the declared traits of the plasma response.
- Returns:
- ResponseTraits
With memory, field grid only, vector-isotropic, consuming the density and pre-ionisation paths.
Examples
>>> from solprop.nonlinear.optmat_rates import OptmatRate >>> from solprop.nonlinear.responses import PlasmaResponse >>> PlasmaResponse("Ar", OptmatRate("Ar", 800e-9)).traits.grids ('field',)
- solprop.nonlinear.responses.Polarisation#
How a response treats the polarisation axis.
alias of
Literal[‘scalar’, ‘vector_isotropic’, ‘tensor’]
- class solprop.nonlinear.responses.ResponseTraits(domain, grids, polarisation, z_profiles)[source]#
Bases:
objectWhat the translation layer must know about a response to compile it.
The four declared traits (see
docs/dev/decisions.md). They are data, not behaviour: the compiler reads them to choose a fused traversal, and a response that lies about them produces a wrong kernel rather than an error.- Attributes:
- domain{‘pointwise’, ‘memory’, ‘convolution’, ‘spectral’}
How the response is evaluated along the time axis.
- gridstuple of {‘field’, ‘envelope’}
The spectral layouts the response is defined on.
- polarisation{‘scalar’, ‘vector_isotropic’, ‘tensor’}
How the response couples the polarisation components.
- z_profilestuple of str
Names of the scalar paths along z the response consumes, for example
("density",). Empty for a response that does not vary withz.
Examples
>>> from solprop.nonlinear.responses import ResponseTraits >>> traits = ResponseTraits("pointwise", ("field",), "scalar", ()) >>> traits.domain, traits.grids ('pointwise', ('field',))
- solprop.nonlinear.responses.Sector#
Which quartets a dispersive cubic keeps: all of them, or the number-conserving ones with two positive and two negative signed legs (SEP 6, Phase 0, decision Q4).
alias of
Literal[‘full’, ‘conserving’]
- solprop.nonlinear.responses.dispersive_cubic(field_t, weights, *, kind, sector, carrier=None, window=None)[source]#
Return SEP 6’s dispersive cubic of a field, per unit
eps0 gamma3_0 rho.The forms Phase 0 derived and holds to an explicit triplet sum. The symmetric evaluator is one product of the filtered field,
G[(G E)^3], withGthe multiplierg. The polynomial evaluator is the grouped sum\[[1 + a\tilde d + (b + b')\tilde d^2]\,E^3 + [3a + 6b\tilde d]\,E^2\tilde DE + 3(b + b')\,E^2\tilde D^2E + 6b\,E(\tilde DE)^2,\]each bracket an output multiplier, with
D~the bounded leg weight. A product of three fields is the isotropic Kleinman contraction of_isotropic, and the sector restricts it to the quartets kept (_product_spectrum). The output multipliers commute withPi_+and with taking the real part, so every product’s spectrum is weighted and summed before one inverse transform.The temporal collar
wmultiplies each product in time, beforePi_+and the output multipliers:G[w (G E)^3], and the polynomial’s output multipliers act on the windowed products (decided by John Travers, 2026-10-08, SEP 6 Phase 2a, open question 1). With a unit collar this is the operator without one; the order matters only where the collar varies.The products are formed on the fine grid, which must hold the cubic’s support for the result to be the convolution it stands for: the same oversampling rule as the constant cubic’s (
conventions.mdsection 3).- Parameters:
- field_tnumpy.ndarray
The field on the fine time grid, shape
(..., n_pol, n_to): realE(t)on a field grid, the complex envelopeA(t)on an envelope grid, in V/m.- weightsDispersiveWeights
The species’ multipliers on the fine grid’s spectral layout.
- kind{‘field’, ‘envelope’}
The spectral layout.
- sector{‘full’, ‘conserving’}
Which quartets are kept.
- carriernumpy.ndarray or None, optional
exp(2 i omega0 t)on the fine grid; required for the full sector on an envelope grid and ignored otherwise.- windownumpy.ndarray or None, optional
The temporal collar
grids.t_window_finein[0, 1], shape(n_to,);Noneis a unit collar.
- Returns:
- numpy.ndarray
The cubic in V^3/m^3 (times m^0: every multiplier is normalised by the static coefficient), real on a field grid and complex on an envelope grid, of the input’s shape.
- solprop.nonlinear.responses.mixture_filter(filters, factors)[source]#
Return the symmetric evaluator’s filter of a gas mixture and its coefficient.
SEP 6, section 6, adopted for the symmetric evaluator of a mixture (decided by John Travers, 2026-10-08, Phase 2b, open question 3): one filter from the mixture’s own Kerr diagonal, \(\gamma_{K,\rm mix}(\nu) = \sum_s N_s\gamma_{K,s}(\tilde \nu_s)\), factorised exactly as section 4.4 factorises a pure gas’s. With \(f_s = \epsilon_0\gamma_{3,s} N_s\), each species’ static coefficient times its density, and \(\hat K_s = g_s^4 = \gamma_{K,s}(\tilde\nu_s)/\gamma_{3,s}\), its own Kerr diagonal under its own leg bound,
\[g_{\rm mix} = \Big[\sum_s f_s \hat K_s \Big/ \sum_s f_s\Big]^{1/4}, \qquad P = \Big(\sum_s f_s\Big)\,G_{\rm mix}[w\,(G_{\rm mix}E)^3].\]On the Kerr diagonal the four legs give \((\sum_s f_s) g_{\rm mix}^4 = \sum_s f_s\hat K_s = \epsilon_0\gamma_{K,\rm mix}\), the mixture’s own degenerate coefficient. The reference is the mixture’s static coefficient \(\sum_s f_s\), so \(g_{\rm mix}(0) = 1\); as in section 4.4 any other positive reference \(\gamma_*\) cancels, moving a factor between the coefficient and \(g^4\). Off the diagonal this approximates the exact per-species sum \(\sum_s f_s G_s[w (G_s E)^3]\), as Phase 2b measured; for one species it is that species’ operator, which this function returns unchanged rather than recomputed.
The filter depends on the composition \(f_s / \sum f_s\), not on the total density, so a backend rebuilds it wherever the composition changes along z. The densities are non-negative (the problem’s domain check), so \(g_{\rm mix}^4\) is a convex combination of the species’ \(\hat K_s\); at a vacuum, where every \(f_s\) is zero, the response is zero whatever the filter, which is then taken as one.
- Parameters:
- filtersnumpy.ndarray
Each species’ own symmetric filter \(g_s\) on one spectral layout, dimensionless, shape
(n_species, n_omega_fine).- factorsnumpy.ndarray
\(f_s\) per species, in C/V^3 (
DispersiveKerrResponse.coefficienttimes the number density in m^-3), non-negative.
- Returns:
- tuple
(sum_s f_s, g_mix): the mixture’s static coefficient and its filter, shape(n_omega_fine,). One species returns its own factor and filter.
Examples
>>> import numpy as np >>> from solprop.nonlinear.responses import mixture_filter >>> filters = np.array([[1.0, 2.0], [1.0, 1.0]]) >>> total, g = mixture_filter(filters, np.array([1.0, 1.0])) >>> total, float(g[0]), float(g[1] ** 4) (2.0, 1.0, 8.5)
- solprop.nonlinear.responses.thg_carrier(omega0, t)[source]#
Return the carrier factor an envelope-grid third-harmonic term needs.
On an envelope grid the state is
A(t) = E_a(t) exp(-i omega0 t)(docs/dev/conventions.mdsection 4), so the part of the cubic response that radiates near3 omega0carries an explicitexp(2 i omega0 t)to place it at the right offset from the carrier. On a field grid nothing is needed: the harmonic is already in the cubic.The
+2isign follows fromE_a = A exp(+i omega0 t)in the internal FFT convention and is derived indocs/guide/media/nonlinear.md; it depends on the envelope convention, so it is derived here rather than transcribed.- Parameters:
- omega0float
Carrier angular frequency, rad/s, positive.
- tnumpy.ndarray
Time samples of the fine grid, s, shape
(n_to,).
- Returns:
- numpy.ndarray
exp(2 i omega0 t), complex, shape(n_to,).
- Raises:
- GridError
If
omega0is not positive.
Examples
>>> import numpy as np >>> from solprop.nonlinear.responses import thg_carrier >>> carrier = thg_carrier(1.0, np.array([0.0, np.pi / 2])) >>> bool(np.allclose(carrier, [1.0, -1.0])) True