solprop.backends.numpy_ref.general_modal#
The general modal guide’s nonlinear source, on the reference backend.
This is a second execution path beside solprop.backends.numpy_ref.passes, not
an extension of it: the legacy pipeline synthesises with one real static matrix in the
time domain, and a general guide’s synthesis is a complex, frequency-dependent map of
three Cartesian components, so it happens in the frequency domain instead.
The pipeline of one stage, for a chunk of quadrature nodes:
Scale. Multiply the coarse channel spectra by the pad scale
n_to / n_t, which keeps the two time-domain signals equal at coincident samples (conventions.mdsection 3), and by the analytic-signal doubling. Both are applied here and not folded intoS, becauseSis the declared physical profile in V/(m sqrt(W)) that the guide’s provider declares.Synthesise.
E(q, c, w) = sum_m S(q, c, m, w) a(m, w)at the chunk’s nodes, on the coarse grid the state lives on, placed into the fine spectrum atgrids.crop_index; then one complex inverse transform per node and component of that doubled one-sided spectrum, which is the analytic signalE_aofconventions.mdsection 4 exactly:E(t) = Re E_a(t)and|E_a|^2is the instantaneous power. A complex profile can only act on an analytic signal. Synthesising before the padding rather than after it is the same arithmetic – every fine bin outsidecrop_indexmultiplies a zero amplitude – over a mapn_omega_fine / n_omegatimes smaller.Respond.
P_i = eps0 sum_jkl A(q)_ijkl E_j E_k conj(E_l)per node, withA(q)the sum ofchi3 Tover the responses of that node’s material region (solprop.translate.responses.isotropic_kerr_tensor()). A node whose region names no response has no polarisation. Which fieldEis here is the record’s own declaration, read back from itsmodel_id: the real fieldRe E_afor the real cubicP = eps0 chi3 (E.E) E, which is the legacy field route’s model and radiates the third harmonic, or the analytic signal itself forP = eps0 chi3 (2 |E|^2 E + (E.E) E*) / 4, which carries the fundamental band alone. The tensor carries the normalisation that belongs to each. The analytic form is contracted densely, over the pair historyE_k conj(E_l)and thenj. The real cubic is symmetric in(j, k, l), so it is evaluated as the symmetric contraction (symmetric_cubic()): ten monomials(E_j E_k) E_lwithj <= k <= lper sample, against coefficients that sum each node’s tensor over each monomial’s orderings (symmetric_kerr_coefficients()), formed once per node and evaluation. That is exact for any tensor, and it is the reduction order every backend runs.Project. The physical polarisation is
Re P_a, so its one-sided spectrum – the same real forward transform every other route takes – is what drives the one-sided state. Contract it withQon the coarse grid and multiply by the record’ssource_frequency_factor,-i omega.
The crop scale n_t / n_to stays in responses.prefactor, where it is on every
route, and the spectral collar
and the interaction picture are applied by the shared
solprop.backends.numpy_ref.passes.stage_output().
The delayed response. A material region may declare the silica tensor Raman model
beside its Kerr response (solprop.nonlinear.raman.SilicaTensorResponse), and
step 3 then adds its polarisation: six real symmetric pair histories
Q_kl = E_k E_l per node, convolved on a doubled axis with that response’s trace
and rank-two kernels, and contracted with chi3 D[term] and the local field. The
instantaneous tensor already carries the 1 - f_R that leaves, so the two shares
add to the region’s one chi3.
What this route does not do. The analytic-signal form radiates no third harmonic, which is why it is not the route’s default: it remains selectable, and is the tested oracle for the fundamental band that the real cubic is measured against.
Memory in the real form. The chunk buffers of step 3 are float64 rather than
complex128 when the field is real, and the symmetric contraction holds six pair
products, a monomial and two component-wide sums per node and sample in place of the
pair history and the contracted tensor (the pair history too when a delayed response
needs its drive): at most 22 doubles against the 36 that general_memory()’s
general_node_pair and general_node_weighted rows count for three components,
so those rows are an upper bound in that form. The bound is the one a budget needs and
the ledger is not split by form. The symmetric coefficients are formed per chunk from
the tensor, n_components * n_monomials doubles a node, a transient like the
others the ledger leaves out.
Module Attributes
Independent entries of the real symmetric pair history of three components. |
Functions
|
Return one chunk's delayed polarisation, in C/m^2, from its pair history. |
|
Return one chunk's dispersive polarisation, in C/m^2, region by region. |
|
Return the full symmetric pair history from its six independent entries. |
|
Return the bytes the general source holds, one row per named buffer. |
|
Evaluate one stage's modal source through the general guide's maps. |
|
Return the real cubic |
|
Sum a per-node tensor over the orderings of each monomial of the real cubic. |
|
Return the real cubic's monomials |
Classes
|
The arrays one general modal problem's source pass reads, at one position. |
- class solprop.backends.numpy_ref.general_modal.GeneralMaps(synthesis, projection, tensor, frequency_factor, node_chunk, form='analytic', node_response=None, delayed_tensor=None, delayed_spectrum=None, synthesis_terms=None, projection_terms=None, dispersive=None, node_region=None)[source]#
Bases:
objectThe arrays one general modal problem’s source pass reads, at one position.
Derived once at construction from the problem’s leaves. The route propagates constant geometry, so the maps have one interval of one Chebyshev degree along z and there is nothing to evaluate per stage.
- Attributes:
- synthesisnumpy.ndarray
Son the coarse grid, complex128, shape(n_nodes, n_components, n_modes, n_omega), in V/(m sqrt(W)).- projectionnumpy.ndarray
Qon the coarse grid, complex128, shape(n_modes, n_nodes, n_components, n_omega), in V m/sqrt(W).- tensornumpy.ndarray
The summed
chi3 Tof each node’s material region, float64, shape(n_nodes, n_components, n_components, n_components, n_components), in m²/V². Zero at a node whose region does not respond.- frequency_factornumpy.ndarray
-i omega, complex128, shape(n_omega,), in rad/s.- node_chunkint
Nodes the pass handles at a time, at least one.
- node_responsenumpy.ndarray
Which response owns each node, int32, shape
(n_nodes,),-1where a node’s region declares none. It is what lets the delayed convolution run once per response over that response’s own nodes, with its own kernel.- delayed_tensornumpy.ndarray
chi3_r D[term]of each response and channel, float64, shape(n_responses, n_terms, n_components, n_components, n_components, n_components), in m²/V². Empty when no region declares a delayed response.- delayed_spectrumnumpy.ndarray
The real transform of each kernel on the doubled fine axis, times the temporal weight, complex128, shape
(n_responses, n_terms, n_to + 1), in s/s. Empty with delayed_tensor.- form{‘analytic’, ‘real’}
Which field the tensor is contracted against: the analytic signal, whose polarisation carries the fundamental band alone, or the real three-component field, whose cubic carries the third harmonic. Read from the record’s
model_idbysolprop.translate.general.node_kerr_form(); tensor already carries the normalisation that goes with it.- dispersivesolprop.translate.responses.DispersiveKerrTerms or None
The gas regions’ dispersive channels (SEP 6), each acting at the nodes of its own region, or
None.- node_regionnumpy.ndarray or None
Each node’s material region, int32, shape
(n_nodes,): where each dispersive channel acts. Present with dispersive.
- at(weights)[source]#
Return the maps a stage evaluates at, from its
transform_weights.A constant guide’s maps are returned as they are, whatever weights says, which keeps its arithmetic bit for bit what it was. A moving guide’s are the weighted sums of the stored coefficients, exactly as
dPhiis the weighted sum of its terms (docs/dev/kernel-contract.mdsection 1).- Parameters:
- weightsnumpy.ndarray or None
The stage bundle’s
transform_weights, float64, one per term;Nonefor maps that do not move.
- Returns:
- GeneralMaps
The maps at the stage’s position.
- Raises:
- BackendError
If moving maps are asked for with no weights, or constant ones with some.
- delayed_spectrum: Complex[ndarray, 'n_responses_spectrum n_terms_spectrum n_rfft_spectrum'] | None = None#
- 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'] | None = None#
- dispersive: DispersiveKerrTerms | None = None#
- frequency_factor: Complex[ndarray, 'n_omega_factor']#
- property moving: bool[source]#
Whether the maps move along z.
True when the synthesis and projection are Chebyshev terms a stage combines over its
transform_weights.
- property n_modes: int[source]#
How many channels the guide propagates.
The channel axis of
synthesis.
- property n_nodes: int[source]#
How many quadrature nodes the guide’s rule carries.
The node axis of
synthesis.
- property n_terms: int[source]#
How many delayed channels the maps carry.
Zero when no region declares a delayed response.
- projection: Complex[ndarray, 'n_modes_q n_nodes_q n_components_q n_omega_q']#
- projection_terms: Complex[ndarray, 'n_map_terms_q n_modes_mq n_nodes_mq n_components_mq n_omega_mq'] | None = None#
- synthesis: Complex[ndarray, 'n_nodes_s n_components_s n_modes_s n_omega_s']#
- synthesis_terms: Complex[ndarray, 'n_map_terms_s n_nodes_ms n_components_ms n_modes_ms n_omega_ms'] | None = None#
- tensor: Float[ndarray, 'n_nodes_t n_components_t_i n_components_t_j n_components_t_k n_components_t_l']#
- solprop.backends.numpy_ref.general_modal.PAIR_HISTORIES = 6#
Independent entries of the real symmetric pair history of three components.
Q_kl = E_k E_lis symmetric, soxx, yy, zz, xy, xz, yzcarry all of it and the convolution – the expensive part of a delayed response – runs six times per node and not nine.
- solprop.backends.numpy_ref.general_modal.delayed_polarisation(field, pair, maps, owner)[source]#
Return one chunk’s delayed polarisation, in C/m^2, from its pair history.
Six real symmetric histories per node, convolved with each response’s own kernel on the doubled fine axis – so the tail of a hundred-femtosecond response cannot wrap onto the head of the drive – and contracted with that response’s
chi3 D[term]and the local field.The loop is over responses rather than nodes because a convolution is not linear in its kernel’s owner: two material regions may declare different delayed models, and each one’s nodes are convolved with its own kernel.
- Parameters:
- fieldnumpy.ndarray
The local field at the chunk’s nodes, shape
(n_nodes, 3, n_to), V/m: the real field for the real cubic and the analytic signal otherwise.- pairnumpy.ndarray
The real drive
Q_kl, shape(n_nodes, 3, 3, n_to), V²/m².- mapsGeneralMaps
The problem’s maps, carrying the delayed tensors and kernel spectra.
- ownernumpy.ndarray
Which response owns each node of the chunk,
-1for none.
- Returns:
- numpy.ndarray
The delayed polarisation, shape
(n_nodes, 3, n_to), C/m².
- solprop.backends.numpy_ref.general_modal.dispersive_polarisation(field, terms, node_region, densities)[source]#
Return one chunk’s dispersive polarisation, in C/m^2, region by region.
Each channel of SEP 6’s dispersive cubic – one species of one region’s gas fill – acts at the nodes of its own region, on the real three-component field:
P = coefficient rho cubic(E)for the polynomial, and for the symmetric evaluator one cubic per region, under its gas fill’s mixture filter (evaluations()), bysolprop.nonlinear.responses.dispersive_cubic(), the arithmetic of the referenceevaluate(), whose isotropic Kleinman contraction is the real cubic’s tensor (SEP 6, decision Q3). The sector is the leaf’s: the full cubic besidekerr_form="real", the number-conserving one beside"analytic", and both are real polarisations, which the projection takes as it takesRe P_a.- Parameters:
- fieldnumpy.ndarray
The real field at the chunk’s nodes,
Re E_a, shape(n_nodes, n_components, n_to), in V/m.- termssolprop.translate.responses.DispersiveKerrTerms
The compiled channels, carrying their node regions.
- node_regionnumpy.ndarray
Each node’s material region.
- densitiessequence of float
Each channel’s number density, m^-3.
- Returns:
- numpy.ndarray
The polarisation, shape
(n_nodes, n_components, n_to), C/m^2; zero at a node whose region has no channel.
- solprop.backends.numpy_ref.general_modal.expand_pair_history(stored)[source]#
Return the full symmetric pair history from its six independent entries.
- Parameters:
- storednumpy.ndarray
The six entries
xx, yy, zz, xy, xz, yz, shape(n_nodes, 6, n_to), in V²/m².
- Returns:
- numpy.ndarray
The symmetric
(n_nodes, 3, 3, n_to)history, in V²/m².
- solprop.backends.numpy_ref.general_modal.general_memory(maps, tables, n_to)[source]#
Return the bytes the general source holds, one row per named buffer.
Three groups. The execution maps
SandQand the per-node tensor are derived once. The reference the record carries – the dense E and H, their reciprocal partners and the coefficient dual – is resident for the whole run because the problem owns it, and is counted here although this pass reads none of it: it is on the far side of the seam, it is the largest group on a guide with many channels, and a budget that did not see it would be wrong by that much. The chunk buffers are the pass’s own, and shrink withnode_chunk.Per-stage transients are not counted: the doubling weights, the inverse transform’s output beside the zero-filled buffer it replaces, the conjugated field, the contiguous copy
rfftmakes of a strided real view and its fine one-sided output before the crop. Measured withtracemallocon a 288-node strand, they add 6.2 % to the rows below at their peak (2.52 MiB over 40.9 MiB); each is freed inside the pass.- Parameters:
- mapsGeneralMaps
The derived maps of one problem.
- tablessolprop.translate.modal_tables.ModalTransformTables
The record the maps came from, whose stored reference is resident.
- n_toint
Fine time samples of the run’s grid, positive.
- Returns:
- dict
Byte counts keyed by buffer name, each counted once:
general_synthesis,general_projection– every coefficient of a moving guide’s maps –general_stage_maps, the two maps a moving guide’s stage evaluates (zero otherwise), andgeneral_tensor; the resident referencegeneral_electric,general_magnetic,general_backward_electric,general_backward_magnetic,general_reciprocal_gramandgeneral_coordinate_dual, zero where the record leaves one empty; and the chunk buffersgeneral_scaled_state,general_node_spectrum,general_node_field,general_node_pair,general_node_weighted,general_node_polarisationandgeneral_node_source.
- solprop.backends.numpy_ref.general_modal.general_source(state, maps, crop_index, *, n_to, pad_scale, transform_weights=None, dispersive_densities=())[source]#
Evaluate one stage’s modal source through the general guide’s maps.
The four steps of the module docstring, in the cubic
maps.formdeclares, with the gas regions’ dispersive channels added at step 3 (dispersive_polarisation()). The result is the projected polarisation times-i omega:Qis in V m/sqrt(W) andPin C/m^2, so the product carriesC V m^-1 W^(-1/2) s^-1, andC Vis a joule – the unit is sqrt(W)/m, the amplitude rateda/dzitself. The crop scalen_t / n_tothatresponses.prefactorcarries is a dimensionless grid convention and changes the number but not the unit.- Parameters:
- statenumpy.ndarray
The lab-frame channel spectra, complex128, shape
(n_modes, n_pol, n_omega), in the square root of watts, withn_polone.- mapsGeneralMaps
The guide’s synthesis, projection, per-node tensor and frequency factor.
- crop_indexnumpy.ndarray
Where each coarse bin sits in the fine spectrum,
numpy.intp, shape(n_omega,).- n_toint
Fine time samples, positive and even.
- pad_scalefloat
n_to / n_t, the oversampling scale ofconventions.mdsection 3.- transform_weightsnumpy.ndarray or None, optional
The stage bundle’s weights of a moving guide’s maps (
GeneralMaps.at());Nonefor maps that do not move.- dispersive_densitiessequence of float, optional
Each dispersive channel’s number density at the stage, m^-3, in the channels’ order: the stage bundle’s
dispersive_kerr_densities.
- Returns:
- numpy.ndarray
The modal source on the coarse grid, complex128, shape
(n_modes, n_pol, n_omega), in sqrt(W)/m before the crop scale.
- Raises:
- BackendError
If the densities do not match the dispersive channels.
- solprop.backends.numpy_ref.general_modal.symmetric_cubic(coefficients, field)[source]#
Return the real cubic
sum_jkl A_ijkl E_j E_k E_lby its symmetric form.The reduction order of the real cubic on every backend: each sample forms the pair products
E_j E_k(j <= k), each monomial as(E_j E_k) E_l, and each component assum_m C_im monomial_mover the monomials in their order, sequential from zero. Against the densesum_j (sum_kl A_ijkl E_k E_l) E_jthat is 3 + 10 products a component in place of 81 + 3.Rounding. With
M_i = sum_jkl |A_ijkl| |E_j| |E_k| |E_l|: a coefficient is withingamma_5of its exact sum, a monomial withingamma_2, and the ten-term sum of products withingamma_10, so the component is withingamma_17 M_iof exact arithmetic; the dense order is withingamma_14(the pair, nine products, the field and three terms), so the two orders differ by at mostgamma_31 M_i.- Parameters:
- coefficientsnumpy.ndarray
symmetric_kerr_coefficients()of the nodes’ tensors, in m²/V².- fieldnumpy.ndarray
The real field at the nodes, float64, shape
(n_nodes, n_components, n_to), in V/m.
- Returns:
- numpy.ndarray
The cubic before
eps0, float64, shape(n_nodes, n_components, n_to), in V/m.
- solprop.backends.numpy_ref.general_modal.symmetric_kerr_coefficients(tensor)[source]#
Sum a per-node tensor over the orderings of each monomial of the real cubic.
On a real field
sum_jkl A_ijkl E_j E_k E_l = sum_m C_im (E_j E_k) E_lover the monomials ofsymmetric_monomials(), withC_imthe sum ofA_ijklover every ordered triple that sorts to monomialm: one ordering forx x x, three forx x y, six forx y z. That is an identity for any tensor, symmetric or not. The orderings are added in lexicographic order of(j, k, l), each sum sequential from zero, which is the order every backend forms them in; a coefficient is withingamma_5of the exact sum relative to the sum of its terms’ magnitudes.- Parameters:
- tensornumpy.ndarray
The per-node
chi3 T, float64, shape(n_nodes, n_components, n_components, n_components, n_components), in m²/V².
- Returns:
- numpy.ndarray
The coefficients, float64, shape
(n_nodes, n_components, n_monomials), in m²/V².
Examples
>>> from solprop.backends.numpy_ref.general_modal import ( ... symmetric_kerr_coefficients, ... ) >>> from solprop.translate.responses import isotropic_kerr_tensor >>> coefficients = symmetric_kerr_coefficients(isotropic_kerr_tensor("real")[None]) >>> coefficients[0, 0, :4] # x: xxx, xxy, xxz, xyy array([1., 0., 0., 1.])
- solprop.backends.numpy_ref.general_modal.symmetric_monomials(n_components)[source]#
Return the real cubic’s monomials
(j, k, l),j <= k <= l, in order.The order is lexicographic, and it is the order
symmetric_cubic()sums them in on every backend: ten for three components, four for two, one for one.- Parameters:
- n_componentsint
Cartesian components of the local field, one to three.
- Returns:
- tuple
The component triples.
Examples
>>> from solprop.backends.numpy_ref.general_modal import symmetric_monomials >>> symmetric_monomials(2) ((0, 0, 0), (0, 0, 1), (0, 1, 1), (1, 1, 1))