solprop.translate.responses#

Response definitions compiled into one fused time-domain pass.

A response says what polarisation a medium produces from the field at a point (solprop.nonlinear.responses); a backend needs coefficients, a table and an ordering it can execute without knowing any physics. This module performs that compilation for the responses that exist: the instantaneous Kerr term, the delayed Raman term, the ionisation rate as a spline table, the UPPE prefactor and the temporal absorber. Section 5 of docs/dev/kernel-contract.md is the class CompiledResponses, field for field.

What is not compiled yet is refused rather than half-built. The spectral-domain responses have a field in the contract and no compiler here, so it stays empty and its flag stays false; anything else raises a solprop.exceptions.TranslationError naming the response, so nothing reaches a backend that would evaluate a term it does not have. A response that declares it is undefined on the problem’s grid is refused with that reason: the plasma model is written for a resolved carrier and has no envelope form.

The Raman fraction is not this module’s business. A response that declares fraction is one whose author already reduced the Kerr coefficient beside it – solprop.nonlinear.gnlse.kerr_susceptibility() does exactly that for the GNLSE – and a molecular gas’s Raman response is absolute, with the full electronic chi3 beside it. Applying (1 - f_R) here would either double the reduction or invent one, and would reduce the Kerr term even with Raman switched off.

The Raman drive form is a property of the problem. Whether a field grid’s drive keeps the 2 omega beat is not a property of each response: a drive that keeps it radiates a third harmonic through the later multiplication by the field, and a grid built without one does not oversample enough to hold it. So the compiler checks that the declared Kerr and Raman responses agree about thg on a field grid, and refuses a field-grid Raman response with no Kerr response beside it. That is why the answer is a field of RamanCoefficients rather than of either response.

The prefactor carries the crop scale of the oversampling round trip; the pad scale is in the synthesis, for the reason given in solprop.translate.modal. It is zero outside the active band rather than one: raw, unnormalised polarisation left in the out-of-band bins would be seen by every Runge-Kutta stage and every error estimate.

It also follows z. Re n_eff(omega, z) moves with the gas and the bore, so on a gradient fill or a taper the prefactor is not one array but a path. Rather than being evaluated afresh at every stage, it is resolved once, the way Phi is and in whichever of Phi’s two representations the mode set allows: an expansion in the scalar paths the modes declare, with the plain products of the basis polynomials as its weights because the prefactor is a value at z and not a cumulative one, or an adaptive Chebyshev table of z over the same span and split on the same declared kinks. Either is one pass per stage through prefactor_at(). A mode set that does not vary along z carries the array instead, in prefactor_constant, and a backend takes that fast path.

Module Attributes

THG_OVERSAMPLING

Least oversampling a third-harmonic term needs, so that 3 omega_max is resolved.

KerrForm

Which cubic a general modal guide's instantaneous response is.

KERR_FORM_MODEL_IDS

The model identity each instantaneous form is recorded under.

KERR_MODEL_FORMS

The inverse of KERR_FORM_MODEL_IDS, which a backend reads a form back from.

DELAYED_MODEL_ID

The model tag a compiled record carries when a region declares a delayed term.

MODEL_ID_SEPARATOR

What joins the model tags of one compiled record's model_id.

Functions

compile_responses(responses, grid, mode, *, ...)

Compile a set of responses into the nonlinear term a kernel executes.

dispersive_regions(responses, grid, *, vector)

Classify a run's active band for each dispersive response it declares.

dispersive_terms(responses, labels, ...[, ...])

Sample the dispersive responses' multipliers on a grid's fine layout.

instantaneous_model_id(form, *, delayed)

Return the model_id a compiled regional record carries.

isotropic_kerr_tensor([form])

Return the dimensionless rank-four tensor of an isotropic Kerr medium.

kerr_coefficients(response, grid)

Reduce a Kerr response on a grid to the numbers a fused pass needs.

plasma_coefficients(response, field_range, ...)

Reduce a plasma response to the coefficients a fused pass needs.

prefactor_at(responses, z, *[, helpers])

Return the prefactor at one position, taking the fast path when there is one.

raman_coefficients(raman, kerr, grid, ...[, ...])

Compile a Raman response into the leaf of section 5, or None.

regional_responses(chi3, regions, *[, ...])

Compile one material region's declared responses into the seam's leaf.

response_kinks(responses[, density])

Return where the response paths kink, each with the name of its path.

unlanded_kinks(kinks, hard_kinks, z_max, *, ...)

Return the response kinks inside the run that no step is made to land on.

Classes

CompiledResponses(response_order, ...[, ...])

The nonlinear term of the propagation equation, ready for a kernel.

DispersiveKerrTerms(evaluator, sector, ...)

SEP 6's frequency-dependent electronic response, reduced to arrays.

KerrCoefficients(coefficient, cubic_form, ...)

The instantaneous third-order polarisation, reduced to numbers.

PlasmaChannel(charge_mass_ratio, ...[, ...])

Bind an independent exposure scan to a labelled population.

PlasmaCoefficients(charge_mass_ratio, ...)

The plasma response, reduced to two constants, a path and a table.

RamanCoefficients(undamped, decay_rates, ...)

The delayed response of a Raman-active medium, as a fused pass needs it.

RegionalResponseTables(response_region, ...)

Say which material responds at each node, and with what tensor and kernel.

ResponseStage(*values)

One step of the fused time-domain traversal, in execution order.

class solprop.translate.responses.CompiledResponses(response_order, kerr_coefficients, ionisation_table, plasma_channels, raman_terms, density, spectral_responses, prefactor_representation, prefactor, prefactor_expansion, prefactor_constant, temporal_absorber, temporal_absorber_fine, absorber_scheme='nonlinear', separable=None, labels=('medium', ), species_names=('medium', ), species_densities=(), envelope_plasma=None, regional_responses=None, dispersive_kerr=None, plasma_integral=<factory>)[source]#

Bases: object

The nonlinear term of the propagation equation, ready for a kernel.

Section 5 of the kernel contract. Every array is a read-only, C-contiguous copy of the stated dtype.

Attributes:
response_ordertuple of ResponseStage

The fused-pass sequence, in execution order.

kerr_coefficientsKerrCoefficients or None

The compiled Kerr term, or None when no Kerr response was declared.

ionisation_tabletuple of RateTable

Channel rate tables, identity-matched to plasma_channels.

plasma_channelstuple of PlasmaChannel

Independent labelled populations with rates, potentials and density paths.

raman_termstuple of RamanCoefficients

Raman-active components with their amplitude densities and total-density linewidth tables. Each component may carry rotation, vibration or both.

labelstuple of str

Unique component labels, including non-ionising and zero-density components.

species_namestuple of str

Chemical names in label order; a species may occur under several labels.

species_densitiestuple of PiecewiseChebyshev

Number densities in m^-3, in label order.

densitysolprop.translate.chebyshev.PiecewiseChebyshev

Number density along z, in m^-3 for a gas or dimensionless 1 for a medium whose chi3 is already absolute, as a table.

spectral_responsestuple of EnvelopePlasmaAssembly

Typed post-transform assembly; empty or the envelope_plasma record.

envelope_plasmaEnvelopePlasmaAssembly or None

Independent envelope populations, cycle data and spectral multipliers.

prefactor_representation{‘constant’, ‘expansion’, ‘table’}

Which representation carries the prefactor, following section 3’s choice for Phi and made the same way. The first two are the device form; the z table is the oracle’s general representation (solprop.translate.problem.KernelProblem.device_ready).

prefactorsolprop.translate.chebyshev.PiecewiseChebyshev or None

The UPPE or envelope prefactor times the crop scale, as a table along z with value shape (n_modes, n_pol, n_omega), complex128, in m/(V s) per unit polarisation. Purely imaginary and exactly zero outside the active band at every position. The table spans the same z range as Phi. Present when prefactor_representation is "table" and also for "constant", whose one interval of degree 0 is exact; absent for "expansion".

prefactor_expansionsolprop.translate.expansion.ParameterExpansion or None

The same quantity as an expansion in the mode set’s scalar paths, with the plain products of the basis polynomials as its weights rather than their integrals, since the prefactor is not cumulative. Present exactly when prefactor_representation is "expansion".

prefactor_constantnumpy.ndarray or None

The prefactor itself, complex128, shape (n_modes, n_pol, n_omega), present exactly when prefactor_representation is "constant" – every mode reports z_constant – so that a backend takes the array rather than a Clenshaw pass. The mirror of solprop.translate.linop.LinearOperator.phi_constant.

temporal_absorbernumpy.ndarray or None

The temporal collar as a rate in 1/m on the coarse time axis, shape (n_t,), or None.

temporal_absorber_finenumpy.ndarray or None

The same rate on the fine time axis, shape (n_to,), which the "nonlinear" absorber scheme needs, or None.

absorber_scheme{‘split_step’, ‘nonlinear’}

Which of the two exact applications of the temporal rate to use (solprop.translate.masks.AbsorberScheme).

separablesolprop.translate.spatial.SeparableOperator or None

Shared constant spatial operator and prefactor coefficients, present exactly for representation "separable"; identical to linear.separable.

regional_responsesRegionalResponseTables or None

Kernel schema 12: the node-region response assignment of a general modal guide, preserving the existing gas law and table identities. None on every route that goes through the compiled fused pass above.

dispersive_kerrDispersiveKerrTerms or None

Kernel schema 24: SEP 6’s frequency-dependent electronic response, one channel per population, or None.

plasma_integralRunningIntegral

Kernel schema 25: the running-integral rule of the field-grid plasma column (SEP 18), one per problem because the channels share the two current integrals. The plain trapezoid without field plasma channels.

Raises:
TranslationError

If the constant prefactor does not have the table’s value shape, an absorber rate is present without the other, or the three Raman fields are not all present or all absent.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import problem_from
>>> from solprop.translate.responses import compile_responses
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12)
>>> problem = problem_from([0.0, 0.0, -1e-26], omega_ref=grid.omega0, gamma=0.1,
...                        shock=False)
>>> compiled = compile_responses([problem.kerr], grid, problem.mode, density=1.0,
...                              z_max=1.0, shock=False, prefactor_index=1.0)
>>> compiled.response_order
(<ResponseStage.KERR: 'kerr'>, <ResponseStage.ACCUMULATE: 'accumulate'>)
absorber_scheme: Literal['split_step', 'nonlinear'] = 'nonlinear'#
density: PiecewiseChebyshev#
dispersive_kerr: DispersiveKerrTerms | None = None#
property electronic_thg: bool | None[source]#

Return the field grid’s harmonic form, read off the electronic term.

Returns:
bool or None

Whether the electronic cubic keeps the third harmonic on a field grid – the constant Kerr term’s real cubic, or the dispersive term’s full sector – or None with no electronic term.

envelope_plasma: EnvelopePlasmaAssembly | None = None#
property harmonic_carrier: bool[source]#

Return whether the pass needs grids.carrier_2w0.

Returns:
bool

True when an envelope grid’s constant Kerr term radiates the harmonic, or its dispersive term keeps the full cubic.

ionisation_table: tuple[RateTable, ...]#
kerr_coefficients: KerrCoefficients | None#
labels: tuple[str, ...] = ('medium',)#
property n_species: int[source]#

Return the number of labelled populations.

Returns:
int

Length of the species axis, including inactive populations.

plasma_channels: tuple[PlasmaChannel, ...]#
property plasma_coefficients: PlasmaChannel | None[source]#

Return the one-channel leaf for the original specialised execution path.

Returns:
PlasmaChannel or None

The sole channel, or None when there are zero or multiple channels.

plasma_integral: RunningIntegral#
property population_channels: tuple[PlasmaChannel | EnvelopePlasmaChannel, ...][source]#

Return labelled population channels in material order.

Returns:
tuple

Field channels or envelope channels. Their stage density and initial population paths share the same scalar packing order.

prefactor: PiecewiseChebyshev | None#
prefactor_constant: Complex[ndarray, 'n_modes n_pol n_omega'] | None#
prefactor_expansion: ParameterExpansion | None#
prefactor_representation: Literal['constant', 'expansion', 'table', 'separable']#
property prefactor_span: tuple[float, float][source]#

Return the range of z the prefactor covers.

Returns:
tuple of float

(first breakpoint, last breakpoint) in m, the same as Phi’s.

property prefactor_value_shape: tuple[int, ...][source]#

Return the shape of the prefactor at one position.

Returns:
tuple of int

(n_modes, n_pol, n_omega), whichever representation carries it.

property raman_coefficients: RamanCoefficients | None[source]#

Return the one-component Raman leaf for its original arithmetic.

Returns:
RamanCoefficients or None

The sole component, or None when there are zero or multiple terms.

raman_terms: tuple[RamanCoefficients, ...]#
regional_responses: RegionalResponseTables | None = None#
response_order: tuple[ResponseStage, ...]#
separable: SeparableOperator | None = None#
species_densities: tuple[PiecewiseChebyshev, ...] = ()#
species_names: tuple[str, ...] = ('medium',)#
spectral_responses: tuple[EnvelopePlasmaAssembly, ...]#
temporal_absorber: Float[ndarray, 'n_t'] | None#
temporal_absorber_fine: Float[ndarray, 'n_to'] | None#
solprop.translate.responses.DELAYED_MODEL_ID: Final[str] = 'silica-tensor-raman'#

The model tag a compiled record carries when a region declares a delayed term.

class solprop.translate.responses.DispersiveKerrTerms(evaluator, sector, carrier, labels, coefficients, positive, filters, outputs, products, densities, regions=None)[source]#

Bases: object

SEP 6’s frequency-dependent electronic response, reduced to arrays.

The reference is solprop.nonlinear.responses.DispersiveKerrResponse.evaluate(), whose arithmetic solprop.nonlinear.responses.dispersive_cubic() is, and whose multipliers these arrays are, sampled on the problem’s own fine spectral layout at absolute frequency. Each labelled species is its own channel with its own multipliers and density. How the channels combine is the evaluator’s (groups()):

  • the polynomial is the exact per-species sum, P = sum_s coefficient_s rho_s cubic_s(E), which a backend may regroup exactly (species of equal leg bound share their derivative fields);

  • the symmetric evaluator takes, for the channels that act at the same nodes – every channel, or one general modal region’s – one filter from their mixture’s Kerr diagonal, rebuilt from the channels’ densities at every stage: P = F G_mix[w (G_mix E)^3] with F = sum_s coefficient_s rho_s and g_mix = [sum_s coefficient_s rho_s g_s^4 / F]^(1/4) (solprop.nonlinear.responses.mixture_filter(); SEP 6 section 6, adopted for mixtures on 2026-10-08). One species is its own operator exactly.

Attributes:
evaluator{‘symmetric’, ‘polynomial’}

Which evaluator every channel takes.

sector{‘full’, ‘conserving’}

Which quartets are kept: the full cubic (thg=True) or the number-conserving sector (thg=False).

carrierbool

Whether the pass needs grids.carrier_2w0: true exactly for the full sector on an envelope grid, whose harmonic groups sit +-2 omega0 from the envelope’s carrier.

labelstuple of str

The channel’s population label, one per channel, in species order.

coefficientstuple of float

eps0 gamma3_0 per channel, the static coefficient per unit density.

positivenumpy.ndarray

Pi_+ on the fine spectral layout, bool, shape (n_omega_fine,).

filtersnumpy.ndarray

Each channel’s input multiplier, float64, shape (n_channels, n_omega_fine): the symmetric factor g, or the polynomial’s bounded leg weight in m^-2.

outputsnumpy.ndarray or None

The polynomial’s two output multipliers per channel, float64, shape (n_channels, 2, n_omega_fine); None for the symmetric evaluator.

productsnumpy.ndarray or None

The polynomial’s two constant product weights per channel, float64, shape (n_channels, 2), in m^4; None for the symmetric evaluator.

densitiestuple of PiecewiseChebyshev

Each channel’s number density along z, m^-3.

regionsnumpy.ndarray or None

On the general modal route, the node region each channel acts in, int32, shape (n_channels,), non-negative: a region’s gas fill contributes one channel per species, and a node responds to the channels of its own region. None on every other route, where a channel is a population of the run’s material and acts at every node.

Raises:
TranslationError

If the vocabulary, the channel counts or the array shapes disagree.

carrier: bool#
coefficients: tuple[float, ...]#
densities: tuple[PiecewiseChebyshev, ...]#
evaluations(densities)[source]#

Return the cubics the reference evaluates at a stage, and their factors.

The polarisation is sum factor cubic(E; weights) over the returned terms, each acting at the nodes of its channel’s region: one per channel for the polynomial, the exact per-species sum; one per group for the symmetric evaluator, its mixture filter built from the group’s densities here (solprop.nonlinear.responses.mixture_filter()), one species’ its own.

Parameters:
densitiessequence of float

Each channel’s number density at the stage, m^-3, in channel order.

Returns:
tuple of tuple

(channel, factor, weights): the first channel of the term, whose region it acts in; eps0 gamma3_0 rho summed over its channels, C/V^3; and its multipliers.

evaluator: str#
filters: Float[ndarray, 'n_channels n_omega_fine_f']#
groups(keys=None)[source]#

Return the channels that combine, each group in channel order.

For the symmetric evaluator a group is the channels that act at the same nodes, whose mixture filter they share (solprop.nonlinear.responses.mixture_filter()). For the polynomial a group is the channels whose leg-weight rows are equal bit for bit and that act at the same nodes: they share their derivative fields, and summing their coefficient tables is an exact rewriting of the per-species sum.

Parameters:
keyssequence of int or None, optional

A key per channel that must also agree within a group; None takes the leaf’s node regions, or a single key where it has none.

Returns:
tuple of tuple of int

The groups, in the order of their first channel.

labels: tuple[str, ...]#
property n_channels: int[source]#

Return the number of species channels.

Returns:
int

One per labelled population with a dispersive response.

outputs: Float[ndarray, 'n_channels_o 2 n_omega_fine_o'] | None#
positive: Bool[ndarray, 'n_omega_fine']#
products: Float[ndarray, 'n_channels_p 2'] | None#
regions: Int32[ndarray, 'n_channels_r'] | None = None#
sector: str#
weights(channel)[source]#

Return one channel’s multipliers in the reference’s own record.

Parameters:
channelint

The channel’s index.

Returns:
solprop.nonlinear.responses.DispersiveWeights

Views of this leaf’s rows.

solprop.translate.responses.KERR_FORM_MODEL_IDS: Final[Mapping[str, str]] = {'analytic': 'isotropic-kerr', 'real': 'isotropic-kerr-real'}#

The model identity each instantaneous form is recorded under.

The form is a physical model choice and so lives in model_id, which is already a representation tag inside the resume identity. It is not a new leaf field: adding one would move a contract row and bump the kernel schema, and there is nothing else the form needs to carry.

solprop.translate.responses.KERR_MODEL_FORMS: Final[Mapping[str, str]] = {'isotropic-kerr': 'analytic', 'isotropic-kerr-real': 'real'}#

The inverse of KERR_FORM_MODEL_IDS, which a backend reads a form back from.

A model identity outside this mapping is refused by name rather than executed as one of the two, because a caller who declares a model this package does not know must not have it silently evaluated as another.

class solprop.translate.responses.KerrCoefficients(coefficient, cubic_form, self_factor, conjugate_factor, harmonic_factor, analytic_signal, take_real, density=None)[source]#

Bases: object

The instantaneous third-order polarisation, reduced to numbers.

The reference implementation is solprop.nonlinear.responses.KerrResponse.evaluate(), and these coefficients are read off it rather than re-derived, so the compiled pass and the oracle cannot drift apart. Two cubic forms exist, because a field grid resolves the carrier and an envelope grid does not:

"real"

P_p = coefficient rho (sum_q E_q^2) E_p — the full cubic of a real field, which already carries the third harmonic and the counter-rotating terms.

"analytic"

P_p = coefficient rho [self (sum_q |a_q|^2) a_p + conjugate (sum_q a_q^2) conj(a_p)], plus coefficient rho harmonic exp(2 i omega_0 t) (sum_q a_q^2) a_p when the harmonic is wanted on an envelope grid. For one component the first collapses to (3/4)|a|^2 a.

Attributes:
coefficientfloat

eps0 chi3 per unit of the density path, in m^2/V^2 when the density is dimensionless and m^5/V^2 when it is a number density in m^-3. The density is the contract’s density table and multiplies this at every position.

cubic_form{‘real’, ‘analytic’}

Which of the two forms above the pass evaluates.

self_factorfloat

Weight of the (sum |a_q|^2) a_p term; 0.5 in the analytic form.

conjugate_factorfloat

Weight of the (sum a_q^2) conj(a_p) term; 0.25 in the analytic form.

harmonic_factorfloat

Weight of the third-harmonic term; 0.25 on an envelope grid that radiates it, zero otherwise.

analytic_signalbool

Whether the pass must form the analytic signal of the real field first. True on a field grid in the analytic form, false on an envelope grid, where the state already is one.

take_realbool

Whether the polarisation is the real part of the result. True on a field grid.

densityPiecewiseChebyshev or None

The scale multiplying coefficient at each z. A composite uses sum(rho*gamma3) with coefficient=eps0; a single response retains its original coefficient and density operation order. None is used only by the standalone leaf builder.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.responses import KerrResponse
>>> from solprop.translate.responses import kerr_coefficients
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12)
>>> coefficients = kerr_coefficients(KerrResponse(chi3=2e-28, thg=False), grid)
>>> coefficients.cubic_form, coefficients.self_factor
('analytic', 0.5)
analytic_signal: bool#
coefficient: float#
conjugate_factor: float#
cubic_form: str#
density: PiecewiseChebyshev | None = None#
harmonic_factor: float#
self_factor: float#
take_real: bool#
type solprop.translate.responses.KerrForm = Literal['analytic', 'real']#

Which cubic a general modal guide’s instantaneous response is.

"analytic" is P = eps0 chi3 (2 |E|^2 E + (E.E) E*) / 4 on the analytic signal, which carries the fundamental band alone; "real" is P = eps0 chi3 (E.E) E on the real three-component field, which is the field route’s own model and carries the third harmonic with it. The two agree on the fundamental.

solprop.translate.responses.MODEL_ID_SEPARATOR: Final[str] = '+'#

What joins the model tags of one compiled record’s model_id.

The identity is a +-joined list whose first tag is the instantaneous form (KERR_FORM_MODEL_IDS) and whose remaining tags name the delayed models, in compilation order. A backend reads the form back from the first tag and refuses an identity it does not know rather than executing it as one it does.

class solprop.translate.responses.PlasmaChannel(charge_mass_ratio, ionisation_potential, preionisation, rate, label, density, electrons_per_event=1.0)[source]#

Bases: PlasmaCoefficients

Bind an independent exposure scan to a labelled population.

Attributes:
charge_mass_ratiofloat

Free-current coefficient in C²/kg, positive.

ionisation_potentialfloat

Energy paid per event in J, positive.

preionisationsolprop.translate.chebyshev.PiecewiseChebyshev

Initial ionised fraction along z, dimensionless in [0, 1].

ratesolprop.translate.ionisation.RateTable

Rate in s^-1 against field amplitude in V/m.

labelstr

Unique component label in the compiled species order.

densitysolprop.translate.chebyshev.PiecewiseChebyshev

Population number density in m^-3 along z.

electrons_per_eventfloat

Electrons released per ionisation event; exactly 1 in schema 6.

density: PiecewiseChebyshev#
electrons_per_event: float = 1.0#
label: str#
class solprop.translate.responses.PlasmaCoefficients(charge_mass_ratio, ionisation_potential, preionisation, rate)[source]#

Bases: object

The plasma response, reduced to two constants, a path and a table.

Section 5 of the kernel contract. The reference implementation is solprop.nonlinear.responses.PlasmaResponse.evaluate(), and these are the only numbers it needs beyond the field and the density: the free-electron current is (e^2/m_e) int f E dt and the ionisation loss is I_p W (1 - f) E / |E|^2, with f = 1 - (1 - f_0) exp(-int W dt) – the pre-ionised fraction enters inside the exponential, so the ionised fraction never exceeds one.

preionisation is a path along z because the response declares it as one, even where the value the user gave is a plain number: a backend reads one value per plane with a Clenshaw pass and never sees a solprop.profiles.ZProfile. Equality is by identity (eq=False), as it is for every group of the contract that holds arrays.

Attributes:
charge_mass_ratiofloat

e^2 / m_e in C^2/kg, positive: the constant of the free-electron current. Carried rather than assumed so that a backend contains no physical constant.

ionisation_potentialfloat

Ionisation potential of the species in J, positive. It is what fixes the size of the loss current, whose work against the field is exactly one potential per electron freed.

preionisationsolprop.translate.chebyshev.PiecewiseChebyshev

The fraction already ionised before the pulse, as a scalar table along z, with values in [0, 1].

ratesolprop.translate.ionisation.RateTable

The ionisation rate in s^-1 against field amplitude in V/m, the same object CompiledResponses.ionisation_table carries. It is exactly zero below its field_min, which is what makes the loss current’s division by the field finite.

Raises:
TranslationError

If either constant is not positive and finite.

Examples

>>> from solprop.nonlinear.optmat_rates import OptmatRate
>>> from solprop.nonlinear.responses import PlasmaResponse
>>> from solprop.translate.responses import plasma_coefficients
>>> response = PlasmaResponse("Ar", OptmatRate("Ar", 800e-9), preionisation=0.25)
>>> compiled = plasma_coefficients(response, (1e9, 1e11), z_max=1.0)
>>> float(compiled.preionisation(0.5)), compiled.rate.field_min > 0.0
(0.25, True)
charge_mass_ratio: float#
ionisation_potential: float#
preionisation: PiecewiseChebyshev#
rate: RateTable#
class solprop.translate.responses.RamanCoefficients(undamped, decay_rates, fraction, thg, tensor_weights, tensor_capable, label='medium', density=None)[source]#

Bases: object

The delayed response of a Raman-active medium, as a fused pass needs it.

A leaf of section 5 of the kernel contract, like PlasmaCoefficients, so its fields are not rows of that document. It replaces the three separate rows of contract version 2 and carries the drive form, which they had nowhere to put: which real quadratic form drives the coherence on a field grid – E^2, or the cycle-averaged |E_a|^2/2 – is a property of the problem, and the translation layer checks that the declared Kerr and Raman responses agree about it.

Attributes:
undampednumpy.ndarray

Each independently damped term’s undamped response, float64, shape (n_terms, n_to_doubled): causal from t = 0, tapered by the grid’s temporal collar and zero-padded to the doubled grid (solprop.translate.raman.undamped_kernels()). n_terms is 1 for a glass or a vibration-only gas and 2 for a gas with a rotation and a vibration. The rows are h itself and carry no eps0; the response writes it outside the convolution integral (docs/guide/media/raman-numerics.md).

decay_ratestuple of solprop.translate.chebyshev.PiecewiseChebyshev

The decay rate 1 / tau_2 of each term along z as a scalar table, one per row of undamped, composed from the term’s dephasing and the density path (solprop.translate.raman.decay_rate_profiles()). Finite everywhere.

fractionfloat

f_R multiplying the summed kernel, in (0, 1]; 1 for an absolute molecular response.

thgbool

Whether a field grid’s drive keeps the 2 omega beat and so radiates a third harmonic through the later multiplication by the field. Ignored on an envelope grid, whose drive is |A|^2/2 whatever this says, because a quadratic drive cannot carry the beat there.

tensor_weightsnumpy.ndarray

Read-only float64 weights, shape (n_terms, 2), multiplying the trace and traceless effective kernels. Isotropic rows are (1, 0) and rank-two rows (1/4, 3/4). Scalar-only rows use (1, 0) but do not establish vector capability. Every row keeps its own decay rate.

tensor_capablebool

Whether every declared term has an established vector contraction.

labelstr

Stable component label; the standalone leaf builder uses “medium”.

densityPiecewiseChebyshev or None

Component amplitude density in m^-3, distinct from the total density used for linewidths. None is used only by the standalone leaf builder.

Raises:
TranslationError

If the kernel is not two-dimensional, the number of decay-rate tables does not match its rows, or the fraction is outside (0, 1].

Examples

>>> import numpy as np
>>> from solprop.profiles import Constant
>>> from solprop.translate.linop import profile_table
>>> from solprop.translate.responses import RamanCoefficients
>>> rates = (profile_table(Constant(1e12), 0.0, 1.0),)
>>> leaf = RamanCoefficients(
...     np.zeros((1, 8)), rates, 0.18, thg=False,
...     tensor_weights=np.array([[1.0, 0.0]]), tensor_capable=False,
... )
>>> leaf.n_terms, leaf.thg
(1, False)
decay_rates: tuple[PiecewiseChebyshev, ...]#
density: PiecewiseChebyshev | None = None#
fraction: float#
label: str = 'medium'#
property n_terms: int[source]#

Return the number of independently damped terms.

Returns:
int

The number of kernel rows, which is also the number of decay-rate tables.

tensor_capable: bool#
tensor_weights: Float[ndarray, 'n_tensor_terms 2']#
thg: bool#
undamped: Float[ndarray, 'n_terms n_to_doubled']#
class solprop.translate.responses.RegionalResponseTables(response_region, chi3, instantaneous_tensor, delayed_tensor, delayed_kernel, kernel_valid_samples, temporal_weight, model_id, policy_id)[source]#

Bases: Displayed

Say which material responds at each node, and with what tensor and kernel.

The optional leaf of contract section 5 that a general modal guide carries: the numeric arrays of a general response oracle, assigned to the node regions of solprop.translate.modal_tables.ModalTransformTables. The existing gas and glass response owners remain the source of every law; this record is the compiled form of those laws and not a second physics.

The delayed term is the silica tensor Raman model: n_terms is zero for a region that declares the instantaneous cubic alone and two – the trace and traceless rank-two channels – for one that declares a solprop.nonlinear.raman.SilicaTensorResponse beside it. Region indices are node region indices and are never species or plasma channel indices.

Attributes:
response_regionnumpy.ndarray

Non-negative int32 region index per response, shape (n_responses,). Each entry selects the material region of ModalTransformTables.region_index the response acts in.

chi3numpy.ndarray

Third-order susceptibility in m^2/V^2, shape (n_intervals, n_deg, n_responses), as Chebyshev coefficients along z so that a declared density path enters exactly once.

instantaneous_tensornumpy.ndarray

Dimensionless rank-four instantaneous tensor, shape (n_responses, n_components, n_components, n_components, n_components) with one, two or three Cartesian components.

delayed_tensornumpy.ndarray

The same for each delayed term, shape (n_responses, n_terms, n_components, n_components, n_components, n_components), contracted as P_i = eps0 chi3 D_ijkl E_j H_kl with H the convolved pair history, so it is symmetric in its last two indices; the term axis is empty for a purely instantaneous response.

delayed_kernelnumpy.ndarray

Causal response kernels in 1/s on the doubled fine time axis, shape (n_intervals, n_deg, n_responses, n_terms, 2 * n_to): the causal half carries the kernel and the second half is the zero padding that makes a transform product the linear convolution. The Raman fraction is already in them, so a record multiplies by chi3 alone.

kernel_valid_samplesnumpy.ndarray

Int32 count of kernel samples inside each term’s support, shape (n_responses, n_terms): the length of the causal half, beyond which the stored kernel is the zero padding and not the response.

temporal_weightnumpy.ndarray

Non-negative quadrature weight in s per fine time sample, shape (n_to,).

model_id, policy_idstr

Non-empty physical-model and numeric-policy identities. Scalar identities, never arrays, enter resume identity.

Raises:
TranslationError

Naming the field that failed.

Examples

>>> import numpy as np
>>> from solprop.translate.responses import RegionalResponseTables
>>> tables = RegionalResponseTables(
...     np.zeros(1, dtype=np.int32), np.full((1, 1, 1), 2.2e-52),
...     np.ones((1, 3, 3, 3, 3)), np.zeros((1, 0, 3, 3, 3, 3)),
...     np.zeros((1, 1, 1, 0, 16)), np.zeros((1, 0), dtype=np.int32),
...     np.full(8, 1e-15), "isotropic-kerr", "dense-nodes",
... )
>>> tables.n_responses, tables.n_components, tables.n_terms
(1, 3, 0)
chi3: Float[ndarray, 'n_intervals_chi3 n_deg_chi3 n_responses_chi3']#
delayed_kernel: Float[ndarray, 'n_intervals_kernel n_deg_kernel n_responses_kernel n_terms_kernel n_to_doubled_kernel']#
delayed_tensor: Float[ndarray, 'n_responses_delayed n_terms_delayed n_components_delayed_i n_components_delayed_j n_components_delayed_k n_components_delayed_l']#
instantaneous_tensor: Float[ndarray, 'n_responses_instant n_components_instant_i n_components_instant_j n_components_instant_k n_components_instant_l']#
kernel_valid_samples: Int32[ndarray, 'n_responses_support n_terms_support']#
model_id: str#
property n_components: int[source]#

Return how many Cartesian components each tensor contracts over.

Returns:
int

At least one and at most solprop.translate.problem.MAX_N_COMPONENTS.

property n_responses: int[source]#

Return how many responses the record assigns.

Returns:
int

The length of response_region.

property n_terms: int[source]#

Return how many delayed terms each response carries.

Returns:
int

Zero for a purely instantaneous response, two for the silica tensor Raman model’s trace and rank-two channels.

policy_id: str#
response_region: Int32[ndarray, 'n_responses_region']#
temporal_weight: Float[ndarray, 'n_to_temporal_weight']#
class solprop.translate.responses.ResponseStage(*values)[source]#

Bases: StrEnum

One step of the fused time-domain traversal, in execution order.

The translation layer compiles the declared set of responses into a single pass per column rather than into one pass per response. The names are the passes of docs/dev/architecture.md section 8; a backend specialises on the sequence at compile time.

Attributes:
INTENSITYstr

Form |E|^2 (or |A|^2) once, for everything that needs it.

IONISATION_RATEstr

Look the ionisation rate up in its spline table.

PLASMA_SCANstr

Integrate the electron density along t; sequential, three running sums.

PLASMA_CURRENTstr

The plasma current and loss terms.

KERRstr

The instantaneous third-order polarisation.

DISPERSIVE_KERRstr

SEP 6’s frequency-dependent electronic polarisation: filtered fields, their products and the output multipliers on the fine grid.

RAMAN_DRIVEstr

Write the driving term of the Raman convolution.

ACCUMULATEstr

Sum the contributions into P(t) and apply the temporal window.

ENVELOPE_CYCLEstr

Form the averaged rate and both directional moments.

ENVELOPE_POPULATIONstr

Scan cycle-averaged exposure and retain surviving neutrals.

ENVELOPE_SOURCESstr

Form separate Drude drive and loss-current streams before projection.

Examples

>>> from solprop.translate.responses import ResponseStage
>>> ResponseStage.KERR, str(ResponseStage.ACCUMULATE)
(<ResponseStage.KERR: 'kerr'>, 'accumulate')
ACCUMULATE = 'accumulate'#
DISPERSIVE_KERR = 'dispersive_kerr'#
ENVELOPE_CYCLE = 'envelope_cycle'#
ENVELOPE_POPULATION = 'envelope_population'#
ENVELOPE_SOURCES = 'envelope_sources'#
INTENSITY = 'intensity'#
IONISATION_RATE = 'ionisation_rate'#
KERR = 'kerr'#
PLASMA_CURRENT = 'plasma_current'#
PLASMA_SCAN = 'plasma_scan'#
RAMAN_DRIVE = 'raman_drive'#
solprop.translate.responses.THG_OVERSAMPLING = 6.0#

Least oversampling a third-harmonic term needs, so that 3 omega_max is resolved.

conventions.md section 3: a cubic response reaching 3 omega_max needs ffac = 6, and 4 suffices without the harmonic. A grid built with thg=False has ffac = 2 on an envelope grid, so a harmonic term on it would alias silently.

solprop.translate.responses.compile_responses(responses, grid, mode, *, density, z_max, n_pol=1, mode_averaged=True, shock=True, prefactor_index=None, ionisation_field_range=None, absorber=None, absorber_scheme='nonlinear', mandatory_edges=(), margin=0.05, tol=1e-12, n_max=64, max_intervals=512, representation='auto', translated_prefactor=None, mixture=None, regional_responses=None, regional_dispersive=None, reuse_prefactor=None, reuse_raman=None)[source]#

Compile a set of responses into the nonlinear term a kernel executes.

The instantaneous Kerr response, the delayed Raman response and the plasma response are compiled. A response of any other kind raises rather than being silently dropped, because a problem that reaches a backend missing a term it declared would run, converge and be wrong.

A plasma response carries its own ionisation rate, so it supplies the run’s ionisation_table; without one there is no rate table.

A Raman response fills the three rows of section 5 from solprop.translate.raman.build_raman_kernels() and solprop.translate.raman.decay_rate_profiles(), and adds raman_drive to response_order after kerr. It is refused on a two-component problem, which would need a tensor kernel no backend can consume, and on a field grid it is refused without a Kerr response beside it, or beside one that disagrees about thg: the drive form is a property of the problem and reaches a backend through the Kerr row.

Parameters:
responsessequence

The declared responses: at most one solprop.nonlinear.responses.KerrResponse, at most one solprop.nonlinear.raman.RamanResponse and at most one solprop.nonlinear.responses.PlasmaResponse, each defined on this grid.

gridsolprop.grid.TimeGrid

The grid; decides the cubic form and supplies the crop scale.

modeMode or sequence of Mode

The mode, or the ordered set of a multimode problem, whose effective index and effective area enter the prefactor.

densitysolprop.profiles.ZProfile or float

Number density along z in m^-3, or 1.0 for a medium whose chi3 is absolute.

z_maxfloat

Length the run covers, m, positive and finite.

n_polint, optional

Polarisation components, 1 or 2.

mode_averagedbool, optional

Whether to use the mode-averaged prefactor, which folds in the effective-area conversion. Default True.

shockbool, optional

Keep the omega dependence of the prefactor’s numerator, which is the optical shock term. Default True.

prefactor_indexfloat or None, optional

Replace the effective index in the prefactor by this constant. The GNLSE passes 1.0.

ionisation_field_rangetuple of float or None, optional

Lower and upper field amplitude in V/m a plasma response’s rate table must cover; refused without a plasma response. Refused when the rate handed over is already a solprop.translate.ionisation.RateTable, which states its own span.

absorbersolprop.boundaries.Absorber or None, optional

The collar rates; only its temporal part is used here, the spectral part being already inside the linear operator.

absorber_scheme{‘split_step’, ‘nonlinear’}, optional

Which application of the temporal rate the backend will use.

mandatory_edgesarray_like, optional

Positions in z that must be interval edges of the prefactor table. solprop.translate.problem.assemble() passes the cumulative operator’s hard_kinks, which is the union of the mode set’s declared breakpoints, so that the two tables split on the same edges; a standalone caller should do the same, because a table left to locate a kink by bisection spends intervals on it and is less accurate there.

marginfloat, optional

Fraction of z_max the density and prefactor tables extend beyond the end.

tolfloat, optional

Target bound on the uniform error of the tables.

n_maxint, optional

Highest Chebyshev degree tried on an interval of the prefactor table before it is bisected.

max_intervalsint, optional

Interval budget of the prefactor’s tables; exceeding it warns.

representation{‘auto’, ‘expansion’, ‘table’}, optional

Which representation to build for the prefactor, as solprop.translate.linop.build_phi() takes for Phi, and chosen the same way. A z-constant mode set takes the constant fast path whatever this says.

translated_prefactorobject, optional

An already translated normalisation the caller owns, including the temporal crop scaling: spatial geometry’s, or the general modal route’s constant crop scale, whose -i omega lives in its own record instead. A PiecewiseChebyshev, ParameterExpansion or SeparableOperator record. Requires mode=None and mode_averaged=False. The material responses still use the same compiler and validation as modal propagation.

mixtureMixture or None, optional

Explicit species and component paths for a spatial material. Modal assembly also discovers the mixture carried by its modes. None keeps the legacy path.

regional_responsesRegionalResponseTables or None, optional

The compiled per-region response of a general modal guide, carried through to contract section 5’s optional leaf unchanged. It is not compiled here: the general route’s responses belong to the guide’s material regions and are built by regional_responses().

regional_dispersiveDispersiveKerrTerms or None, optional

The compiled dispersive channels of a general modal guide’s gas regions (SEP 6), carried through to dispersive_kerr unchanged, beside regional_responses; they are not compiled here either.

reuse_prefactorParameterExpansion or None, optional

Coefficients of the unchanged prefactor function on the same grid and fixed parameters; range checks decide whether its terms may be shared.

reuse_ramanMapping or None, optional

Labelled undamped rows for unchanged Raman models and time grid. Densities and decay-rate tables are rebuilt.

Returns:
CompiledResponses

Section 5 of the kernel contract.

Raises:
TranslationError

If a response cannot be compiled, more than one response of a kind is given, a Raman response arrives on a problem this layer cannot compile it for, a field range arrives without a plasma response, or an argument is out of range.

Examples

>>> import numpy as np
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import problem_from
>>> from solprop.translate.responses import compile_responses
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12)
>>> problem = problem_from([0.0, 0.0, -1e-26], omega_ref=grid.omega0, gamma=0.1,
...                        shock=False)
>>> compiled = compile_responses([problem.kerr], grid, problem.mode, density=1.0,
...                              z_max=1.0, shock=False, prefactor_index=1.0)
>>> compiled.prefactor.value_shape, compiled.prefactor_constant.shape
((1, 1, 4096), (1, 1, 4096))
>>> bool(np.all(compiled.prefactor_constant[0, 0][~grid.active_mask] == 0.0))
True
solprop.translate.responses.dispersive_regions(responses, grid, *, vector)[source]#

Classify a run’s active band for each dispersive response it declares.

SEP 6, section 4.10: the active band, omega_min / 2 to 1.1 omega_max, holds every output the grid can carry, and each part of it is supported, bounded or best effort for each gas. A run is never refused for the frequencies its field occupies; one whose band leaves a gas’s support is logged once, at INFO, naming the gas and the band.

Parameters:
responsessequence of object

The run’s declared responses; the dispersive ones are classified.

gridsolprop.grid.TimeGrid

The run’s grid.

vectorbool

Whether the field at a node has two polarisation components, which takes the any-polarisation band edge.

Returns:
tuple of solprop.physdata.nonlinear_data.ElectronicRegions

One per dispersive response, in declaration order.

solprop.translate.responses.dispersive_terms(responses, labels, densities, grid, *, regions=None)[source]#

Sample the dispersive responses’ multipliers on a grid’s fine layout.

Parameters:
responsessequence of DispersiveKerrResponse

The declared responses, one per channel, agreeing on evaluator and thg.

labelssequence of str

Each response’s population label.

densitiessequence of PiecewiseChebyshev

Each population’s number density table, m^-3.

gridsolprop.grid.TimeGrid

The grid, whose omega_fine gives the absolute frequency of every bin.

regionssequence of int or numpy.ndarray or None, optional

On the general modal route, the node region of each channel; None elsewhere.

Returns:
DispersiveKerrTerms

The compiled leaf.

Raises:
TranslationError

If the responses disagree on evaluator or thg, or the full cubic is asked for on an envelope grid that does not oversample enough to hold it.

solprop.translate.responses.instantaneous_model_id(form, *, delayed)[source]#

Return the model_id a compiled regional record carries.

Parameters:
form{‘analytic’, ‘real’}

The instantaneous cubic’s form.

delayedbool

Whether the record carries the silica tensor Raman term.

Returns:
str

For example isotropic-kerr-real+silica-tensor-raman.

Raises:
TranslationError

If the form is not one this package compiles, named rather than surfacing as a bare lookup failure – the same refusal isotropic_kerr_tensor() makes.

Examples

>>> from solprop.translate.responses import instantaneous_model_id
>>> instantaneous_model_id("real", delayed=True)
'isotropic-kerr-real+silica-tensor-raman'
solprop.translate.responses.isotropic_kerr_tensor(form='analytic')[source]#

Return the dimensionless rank-four tensor of an isotropic Kerr medium.

The instantaneous third-order polarisation of an isotropic medium in the analytic-signal convention is

P_i = eps0 chi3 (2 |E|^2 E_i + (E.E) conj(E_i)) / 4

(docs/guide/media/nonlinear.md), and writing it as the general contraction P_i = eps0 chi3 T_ijkl E_j E_k conj(E_l) that RegionalResponseTables carries gives Kleinman-symmetric

T_ijkl = (d_ij d_kl + d_ik d_jl + d_il d_jk) / 4,

whose three terms contribute E_i (E.E*), E_i (E.E*) and conj(E_i) (E.E) respectively. The conjugated field is the last index, which is the index convention of the record and of the reference pass that reads it.

On the real three-component field the same contraction is the real cubic P = eps0 T:EEE the legacy field route evaluates, whose scalar limit is P = eps0 chi3 E^3. Each of the three delta terms then contributes E_i (E.E), so the normalisation is a third and not a quarter:

T_ijkl = (d_ij d_kl + d_ik d_jl + d_il d_jk) / 3.

The two are one model in two representations. For a narrowband real field E = Re(A e^(-i w t)) the real cubic’s fundamental component has analytic signal 3 eps0 chi3 |A|^2 A / 4, which is exactly what the analytic form returns; the real cubic carries the third harmonic beside it and the analytic form does not.

Parameters:
form{‘analytic’, ‘real’}, optional

Which field the tensor is contracted against: the analytic signal, the default, or the real three-component field.

Returns:
numpy.ndarray

The tensor, shape (3, 3, 3, 3), dimensionless.

Raises:
TranslationError

If form is neither.

Examples

>>> import numpy as np
>>> from solprop.translate.responses import isotropic_kerr_tensor
>>> field = np.array([0.3 + 0.2j, -0.1 + 0.4j, 0.05 - 0.15j])
>>> direct = (2 * np.vdot(field, field).real * field
...           + (field @ field) * field.conj()) / 4
>>> contracted = np.einsum("ijkl,j,k,l->i", isotropic_kerr_tensor(),
...                        field, field, field.conj())
>>> bool(np.allclose(contracted, direct))
True
>>> real = np.array([0.3, -0.1, 0.05])
>>> cubic = np.einsum("ijkl,j,k,l->i", isotropic_kerr_tensor("real"),
...                   real, real, real)
>>> bool(np.allclose(cubic, (real @ real) * real))
True
solprop.translate.responses.kerr_coefficients(response, grid)[source]#

Reduce a Kerr response on a grid to the numbers a fused pass needs.

Parameters:
responsesolprop.nonlinear.responses.KerrResponse

The response; its chi3 is per unit of the density path.

gridsolprop.grid.TimeGrid

The grid, which decides the cubic form and whether a harmonic term is resolved.

Returns:
KerrCoefficients

The compiled coefficients.

Raises:
TranslationError

If a third-harmonic term is asked for on an envelope grid that does not oversample enough to resolve it, where it would alias into the fundamental without any sign that it had.

Examples

>>> from solprop.constants import EPS0
>>> from solprop.grid import FieldGrid
>>> from solprop.nonlinear.responses import KerrResponse
>>> from solprop.translate.responses import kerr_coefficients
>>> grid = FieldGrid.from_limits(1.0, 800e-9, (200e-9, 4e-6), 200e-15)
>>> compiled = kerr_coefficients(KerrResponse(chi3=1.0), grid)
>>> compiled.cubic_form, compiled.coefficient == EPS0, compiled.take_real
('real', True, True)
solprop.translate.responses.plasma_coefficients(response, field_range, *, z_max, margin=0.05, tol=1e-12)[source]#

Reduce a plasma response to the coefficients a fused pass needs.

The response’s own rate is what the run uses. A solprop.translate.ionisation.RateTable handed to the response is taken as it stands, because it is already the compiled form; anything else is tabulated over field_range, which the caller must state – a table cannot choose its own span.

Parameters:
responsesolprop.nonlinear.responses.PlasmaResponse

The declared response.

field_rangetuple of float or None

Lower and upper field amplitude in V/m the rate table must cover. Ignored, and refused, when the response already carries a table.

z_maxfloat

Length the run covers, m. The pre-ionisation table spans [0, z_max (1 + margin)], the same range as Phi.

marginfloat, optional

Fraction of z_max the table extends beyond the end.

tolfloat, optional

Target bound on the uniform error of the pre-ionisation table.

Returns:
PlasmaCoefficients

The compiled coefficients.

Raises:
TranslationError

If a rate that is not already a table is given without a field range, or a field range is given beside a table that does not need one.

Examples

>>> from solprop.nonlinear.optmat_rates import OptmatRate
>>> from solprop.nonlinear.responses import PlasmaResponse
>>> from solprop.translate.responses import plasma_coefficients
>>> compiled = plasma_coefficients(
...     PlasmaResponse("Ar", OptmatRate("Ar", 800e-9)), (1e9, 1e11), z_max=1.0
... )
>>> f"{compiled.ionisation_potential:.4e}"
'2.5250e-18'
solprop.translate.responses.prefactor_at(responses, z, *, helpers=None)[source]#

Return the prefactor at one position, taking the fast path when there is one.

The one way a backend evaluates the prefactor, as solprop.translate.linop.delta_phi() is the one way it evaluates the linear flow: the array of prefactor_constant when the mode set does not vary, and one Clenshaw pass over the table otherwise. Going through this function rather than reading the table’s arrays keeps the representation the translation layer’s business, which is what lets it change without touching a backend.

Parameters:
responsesCompiledResponses

Section 5 of the kernel contract.

zfloat

Position along the waveguide, m, inside the table’s span.

helperssolprop._helpers.Helpers or None, optional

The evaluating backend’s Chebyshev helpers, for a z table read during a run; None, the default, takes the shared translation-time evaluation.

Returns:
numpy.ndarray

The prefactor times the crop scale, complex128, shape (n_modes, n_pol, n_omega); the stored array itself on the fast path.

Raises:
TranslationError

If z lies outside the table.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import problem_from
>>> from solprop.translate.responses import compile_responses, prefactor_at
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12)
>>> problem = problem_from([0.0, 0.0, -1e-26], omega_ref=grid.omega0, gamma=0.1,
...                        shock=False)
>>> compiled = compile_responses([problem.kerr], grid, problem.mode, density=1.0,
...                              z_max=1.0, shock=False, prefactor_index=1.0)
>>> prefactor_at(compiled, 0.5) is compiled.prefactor_constant
True
solprop.translate.responses.raman_coefficients(raman, kerr, grid, density, *, n_pol, span, tol, undamped=None)[source]#

Compile a Raman response into the leaf of section 5, or None.

The expensive half of the kernel is sampled once per grid and the cheap half becomes one scalar table along z per term, so that what crosses the seam is arrays and paths and no physics object (docs/guide/media/raman-numerics.md, “Sampling the kernel”).

The fraction is solprop.nonlinear.raman.RamanResponse.scale and not fraction itself, so that an absolute molecular response carries the 1 the contract’s row promises rather than a None the other two rows would then have to match.

Parameters:
ramansolprop.nonlinear.raman.RamanResponse or None

The declared response.

kerrKerrResponse or DispersiveKerrResponse or None

The electronic response declared beside it, constant or dispersive, which fixes the field grid’s drive form.

gridsolprop.grid.TimeGrid

The grid supplying the fine time axis and the temporal collar.

densitysolprop.profiles.ZProfile

The number density along z, which the decay rates are composed with.

n_polint

Polarisation components of the state.

spanfloat

End of the tabulated range, m: the same z_max (1 + margin) as Phi.

tolfloat

Target bound on the uniform error of the decay-rate tables.

undampednumpy.ndarray or None, optional

Undamped kernel rows to share for an identical model and fine time grid.

Returns:
RamanCoefficients or None

The compiled coefficients, or None when no Raman response was declared.

Raises:
TranslationError

If two physical components request a scalar-only declaration, or a field grid has no compatible Kerr response to establish the drive form.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.raman import response_for
>>> from solprop.profiles import Constant
>>> from solprop.translate.responses import raman_coefficients
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12)
>>> response = response_for("SiO2", amplitude=1e-22, fraction=0.18, thg=False)
>>> leaf = raman_coefficients(
...     response, None, grid, Constant(1.0), n_pol=1, span=1.0, tol=1e-12
... )
>>> leaf.n_terms, leaf.thg
(1, False)
solprop.translate.responses.regional_responses(chi3, regions, *, raman=None, n_to, dt_fine, form='analytic', policy_id='dense-nodes')[source]#

Compile one material region’s declared responses into the seam’s leaf.

An absolute chi3 per responding region, the isotropic instantaneous tensor of isotropic_kerr_tensor() in the declared form, and – for a region that declares one – the two delayed channels of solprop.nonlinear.raman.SilicaTensorResponse. A node whose region names no response has no polarisation, which is how a linear cladding is declared.

One susceptibility, shared. Where a delayed response is declared, the instantaneous tensor is scaled by its 1 - f_R and the delayed kernels carry (3/2) f_R (solprop.nonlinear.raman.SILICA_TENSOR_AMPLITUDE), so the record multiplies by chi3 once and the two shares add to one chi3 on the fundamental band: a field-domain delayed kernel of integral X acts there like a Kerr term of (2/3) X, because its drive is the cycle average, so (2/3)(3/2) f_R + (1 - f_R) = 1. Every response of one record carries the same number of delayed terms, because the leaf’s term axis is shared; a region with no delayed response gets zero kernels on that axis, which contribute nothing.

chi3 is stored as one Chebyshev coefficient on one interval, because a declared material is constant along a constant guide; a z-varying susceptibility needs the density path of the legacy compiler and is not compiled here.

Parameters:
chi3sequence of float

Absolute third-order susceptibility of each response, m²/V², finite.

regionssequence of int

The node region each response acts in, non-negative, one per chi3.

ramansequence or None, optional

The delayed response each region declares, or None in that slot for a region with none; None for the whole sequence means no region declares one. One entry per chi3.

n_toint

Fine time samples of the run’s grid, positive.

dt_finefloat

Fine sample spacing in s, positive; the temporal quadrature weight.

form{‘analytic’, ‘real’}, optional

Which cubic the instantaneous tensor is: the analytic-signal one, the default, which carries the fundamental band alone, or the real cubic of the field route, which carries the third harmonic. It fixes both the tensor and the first tag of the model_id a backend reads the form back from (KERR_FORM_MODEL_IDS).

policy_idstr, optional

Numeric-policy identity recorded on the leaf.

Returns:
RegionalResponseTables

The validated leaf of contract section 5.

Raises:
TranslationError

If the sequences disagree in length, or the record does not validate.

Examples

>>> from solprop.nonlinear.raman import SilicaTensorResponse
>>> from solprop.translate.responses import regional_responses
>>> tables = regional_responses([2.2e-22], [0], n_to=16, dt_fine=1e-16)
>>> tables.n_responses, tables.n_components, tables.n_terms
(1, 3, 0)
>>> tables.model_id
'isotropic-kerr'
>>> delayed = regional_responses(
...     [2.2e-22], [0], raman=[SilicaTensorResponse()],
...     n_to=16, dt_fine=1e-16, form="real",
... )
>>> delayed.n_terms, delayed.model_id
(2, 'isotropic-kerr-real+silica-tensor-raman')
solprop.translate.responses.response_kinks(responses, density=None)[source]#

Return where the response paths kink, each with the name of its path.

A step lands only on the hard kinks of Phi, which the mode set declares. A response can carry a path the modes know nothing about: a plasma response’s pre-ionisation along z, or a density path given to solprop.translate.problem.assemble() explicitly. Its breakpoints are where the right-hand side is not smooth, so they must become hard kinks as well, or a step straddles them and the integrator loses its order there.

Parameters:
responsessequence of object

The declared responses; the plasma responses’ pre-ionisation paths are read.

densityZProfile, float or None, optional

An explicit density path in m^-3, when one replaces the modes’ own.

Returns:
tuple of tuple

(z, name) pairs, z in m, in declaration order; a number, or a path whose is_constant is true, declares none.

Examples

>>> import numpy as np
>>> from solprop.nonlinear.optmat_rates import OptmatRate
>>> from solprop.nonlinear.responses import PlasmaResponse
>>> from solprop.profiles import PiecewiseLinear
>>> from solprop.translate.responses import response_kinks
>>> path = PiecewiseLinear(np.array([0.0, 0.4, 1.0]), np.array([0.0, 0.1, 0.1]))
>>> rate = OptmatRate("Ar", 800e-9)
>>> response_kinks([PlasmaResponse("Ar", rate, preionisation=path)])
((0.0, 'preionisation'), (0.4, 'preionisation'), (1.0, 'preionisation'))
solprop.translate.responses.unlanded_kinks(kinks, hard_kinks, z_max, *, slack)[source]#

Return the response kinks inside the run that no step is made to land on.

Parameters:
kinkssequence of tuple

(z, name) pairs from response_kinks(), z in m.

hard_kinksnumpy.ndarray

The linear operator’s hard kinks in m, shape (n_kinks,).

z_maxfloat

End of the run in m; a kink at or beyond it, or at or before the entrance, needs no landing.

slackfloat

Distance in m within which a hard kink counts as the same position.

Returns:
tuple of tuple

The (z, name) pairs strictly inside (0, z_max) with no hard kink within slack; empty when every one is landed on.

Examples

>>> import numpy as np
>>> from solprop.translate.responses import unlanded_kinks
>>> kinks = ((0.4, "preionisation"), (0.7, "preionisation"))
>>> unlanded_kinks(kinks, np.array([0.4]), 1.0, slack=1e-12)
((0.7, 'preionisation'),)