solprop.translate.coupled#
The metric and the coupled-flow representation of a general modal guide.
Two leaves of section 3 of docs/dev/kernel-contract.md, added at kernel schema
12: ModalMetricTables, the explicit physical flux metric the controller, the
linear flow and the output layer all read, and CoupledFlowTables, the frozen
or ordered representation of a generator whose channels couple.
CoupledFlowTables is the record, its validation and its printing, built by
constant_schur_flow(), frozen_residual_flow() and cf4_flow() for the
routes whose channels couple – a capillary’s coherent wall and taper coupling, and a
general modal guide whose declared basis or motion couples its channels. Everywhere
else it is None and a guide propagates through the diagonal cumulative flow.
ModalMetricTables admits all three kinds: "identity", "diagonal" and
the "dense" kind a declared unitary change of basis produces
(dense_metric()). Which of them
a backend executes is its own admission, and numpy_ref refuses the diagonal
one, which nothing here builds.
Units are SI throughout (docs/dev/conventions.md): omega in rad/s, z_edges
in m, the generator and every term of it in 1/m, metric entries dimensionless, errors
and bounds dimensionless. Every complex array is in the internal FFT convention,
like the rest of the contract; there is no fourth conversion site.
Module Attributes
Which representation of the physical flux metric a problem carries. |
|
How one interval of a coupled flow is integrated. |
|
Relative coefficient tail a section's z expansion is resolved to. |
|
Highest Chebyshev degree along z tried on one section before it is refused. |
|
The two Gauss-Legendre nodes of a CF4 step, as fractions of its length. |
|
|
|
Default limit on a CF4 section's internal growth bound, dimensionless. |
Functions
|
Return the advisory step limit of every section and frequency, in m. |
|
Return the step-doubling estimate of every CF4 section's local flow defect. |
|
Build the CF4 coupled flow of a varying guide, with its two guards. |
|
Return the internal growth bound of every section, dimensionless. |
|
Build the coupled flow of one constant section, with its certificate. |
|
Build the dense metric of a basis from its own sampled flux Gram. |
|
Return the largest eigenbasis condition the compiled flow actions accept. |
|
Return the tolerance a position is matched to a section edge within. |
|
Return the index of the section a position lies in. |
|
Return the weights that combine a coupled flow's coefficients at one position. |
|
Return the generator one section of a coupled flow is frozen at. |
|
Build the certified coupled flow of a varying guide, frozen section by section. |
|
Return how many z intervals a set of stored positions declares. |
|
Evaluate a basis metric at one statistics or observation plane. |
|
Build the identity metric of a basis certified orthonormal in true power. |
Return the frozen-residual truncation defect of one step, dimensionless. |
|
|
Return the complex Schur factors of a generator, one per frequency. |
|
Return the eigenbasis of each Schur factor, and where it is well conditioned. |
|
Expand an array-valued function of z in Chebyshev coefficients per section. |
|
Return the schema-7 modal identity of a capillary taper's coupling. |
|
Return the schema-7 modal identity of a capillary carrying a coherent wall. |
Classes
|
A generator whose channels couple, frozen or ordered along z. |
|
The explicit physical flux metric of a modal basis. |
- solprop.translate.coupled.CF4_GROWTH_LIMIT: float = 2.0#
Default limit on a CF4 section’s internal growth bound, dimensionless.
A section whose bound
exp(2|a| dz_max (m_hi - m_lo))exceeds it is refused by name (or falls back to"frozen_residual"where that is declared). Two is at most one bit of the state’s scale lost to the product of the internal factors, and corresponds todz_max (m_hi - m_lo) <= 8.96– about the nine e-foldings of the Hermitian spread that the defaultCoherentWall.condition_limitof 1e4 admits. It is a proposal reasoned from that bound, not a threshold established by experiment.
- solprop.translate.coupled.CF4_NODES: tuple[float, float] = (0.21132486540518713, 0.7886751345948129)#
The two Gauss-Legendre nodes of a CF4 step, as fractions of its length.
- solprop.translate.coupled.CF4_WEIGHTS: tuple[float, float] = (-0.038675134594812866, 0.5386751345948129)#
(a, b)of the two-exponential CF4:a + b = 1/2andb - a = 1/sqrt(3).Matching the integral of the generator fixes the sum, and matching the cubic Magnus term
-h^3 [A_0, A_1]/12against the product’s BCH commutator-h^3 (b - a)[A_0, A_1]/(4 sqrt(3))fixes the difference.a = -0.0386751is negative, and is forced by the two-exponential ansatz: it is why a factor of a dissipative flow need not be contractive, and whycf4_flow()guards the growth of its internal factors.
- class solprop.translate.coupled.CoupledFlowTables(omega, z_edges, generator, fixed_terms, term_weights, wall_vectors, wall_weights, connection, generator_error, residual_norm_bound, inverse_condition_bound, section_methods, gauge_id, certificate_id)[source]#
Bases:
DisplayedA generator whose channels couple, frozen or ordered along z.
The leaf of contract section 3 that carries the coherent wall operator and the taper coupling.
numpy_refexecutes three combinations. The first is the dense generator of a single constant section,section_methods == ("constant_schur",), no connection and an exactly zero residual, built byconstant_schur_flow()from a constant coherent block. The second is the dense generator of any number of"frozen_residual"sections at any degree along z, with the taper connection and an explicit residual the Runge-Kutta source carries, built byfrozen_residual_flow()from the same block evaluated along the declared profile. The third is"cf4"sections, built bycf4_flow()from the same expansion, whose whole generator the backend integrates by the commutator-free fourth-order exponential with no explicit residual, mixed with"frozen_residual"sections only where a declared fallback put them. Every other method, representation and mixture is refused by name at the backend, and the compiled backends refuse"cf4"sections.The dense
generatoris optional. The factored alternative combines fixed matrices with scalar weights, rank-one wall terms and connection coefficients without a mandatory dense(z, omega, modes, modes)allocation; exactly one of the two complete representations is stored, never an ambiguous mixture.- Attributes:
- omeganumpy.ndarray
The canonical coarse angular frequencies in rad/s, shape
(n_omega,), increasing and non-negative. A problem’s leaf carriesgrids.omegaitself, whose first bin is zero on a field grid (solprop.translate.problem.KernelProblem.validate()).- z_edgesnumpy.ndarray
Interval edges in m, shape
(n_edges,), increasing.- generatornumpy.ndarray or None
Dense generator in 1/m, shape
(n_intervals, n_deg, n_modes, n_modes, n_omega); the dense representation, mutually exclusive with the factored one.- fixed_termsnumpy.ndarray or None
Fixed matrices in 1/m, shape
(n_terms, n_modes, n_modes, n_omega).- term_weightsnumpy.ndarray or None
Dimensionless scalar weights of those matrices, shape
(n_intervals, n_deg, n_terms); supplied withfixed_terms.- wall_vectorsnumpy.ndarray or None
Rank-one wall vectors, dimensionless, shape
(n_intervals, n_modes, n_omega); supplied withwall_weights.- wall_weightsnumpy.ndarray or None
Their scalar weights in 1/m, shape
(n_intervals, n_deg, n_omega), so thatw (v x v)carries the generator’s own 1/m, andconstant_schur_flow()checks the reconstruction.- connectionnumpy.ndarray or None
Taper connection coefficients in 1/m, shape
(n_intervals, n_deg, n_modes, n_modes, n_omega).- generator_errornumpy.ndarray
Non-negative dimensionless representation error, shape
(n_intervals, n_omega).- residual_norm_boundnumpy.ndarray
Non-negative bound in 1/m on the explicit residual, same shape; zero on a
"cf4"section, which has none.- inverse_condition_boundnumpy.ndarray
Dimensionless bound of at least one on the conditioning of the inverse action, same shape; on a
"cf4"section, on the product of the four factor norms of a forward and inverse pair (cf4_flow()).- section_methodstuple of str
One of
"diagonal","constant_schur","frozen_residual"or"cf4"per interval.- gauge_id, certificate_idstr
Non-empty identities; scalars, never arrays, enter resume identity.
- Raises:
- TranslationError
Naming the field that failed.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import CoupledFlowTables >>> flow = CoupledFlowTables( ... np.linspace(1e15, 2e15, 2), np.array([0.0, 1.0]), ... np.zeros((1, 1, 2, 2, 2), dtype=complex), None, None, None, None, None, ... np.zeros((1, 2)), np.zeros((1, 2)), np.ones((1, 2)), ("diagonal",), ... "lab", "none", ... ) >>> flow.representation, flow.n_modes ('dense', 2)
- connection: Complex[ndarray, 'n_intervals_connection n_deg_connection n_modes_connection_out n_modes_connection_in n_omega_connection'] | None#
- fixed_terms: Complex[ndarray, 'n_terms_fixed n_modes_fixed_out n_modes_fixed_in n_omega_fixed'] | None#
- generator: Complex[ndarray, 'n_intervals_generator n_deg_generator n_modes_generator_out n_modes_generator_in n_omega_generator'] | None#
- generator_error: Float[ndarray, 'n_intervals_generator_error n_omega_generator_error']#
- inverse_condition_bound: Float[ndarray, 'n_intervals_inverse_bound n_omega_inverse_bound']#
- property n_deg: int[source]#
Return how many Chebyshev coefficients along z each section carries.
- Returns:
- int
The degree axis of the dense generator, or one for the factored representation, whose weights carry the z dependence instead.
- property n_modes: int[source]#
Return how many channels the flow couples.
- Returns:
- int
The channel count of the stored representation.
- omega: Float[ndarray, 'n_omega_flow']#
- property representation: Literal['dense', 'factored'][source]#
Return which of the two complete representations is stored.
- Returns:
- {‘dense’, ‘factored’}
"dense"when the generator array is present,"factored"when the fixed matrices and their weights are.
- residual_norm_bound: Float[ndarray, 'n_intervals_residual_bound n_omega_residual_bound']#
- term_weights: Complex[ndarray, 'n_intervals_term_weights n_deg_term_weights n_terms_term_weights'] | None#
- wall_vectors: Complex[ndarray, 'n_intervals_wall_vectors n_modes_wall_vectors n_omega_wall_vectors'] | None#
- wall_weights: Complex[ndarray, 'n_intervals_wall_weights n_deg_wall_weights n_omega_wall_weights'] | None#
- z_edges: Float[ndarray, 'n_edges_flow']#
- solprop.translate.coupled.MAX_SECTION_DEGREE: int = 32#
Highest Chebyshev degree along z tried on one section before it is refused.
A section of a declared taper or pressure stage carries a smooth rational function of
a(z)andrho(z); a degree this high on one section means the section spans structure the caller should have declared or should divide, which is whatCoherentWall.sectionsis for. The search starts atsolprop.translate.chebyshev.INITIAL_DEGREEand doubles.
- solprop.translate.coupled.MetricKind#
Which representation of the physical flux metric a problem carries.
"identity"is an orthonormal power basis, whose metric owns no coefficient array at all;"diagonal"is a per-channel positive weight;"dense"is the full Hermitian matrix a nonorthogonal basis needs, which a declared unitary change of basis produces.alias of
Literal[‘identity’, ‘diagonal’, ‘dense’]
- class solprop.translate.coupled.ModalMetricTables(kind, omega, z_edges, diagonal, dense, error, minimum_eigenvalue, maximum_eigenvalue, gauge_id, certificate_id)[source]#
Bases:
DisplayedThe explicit physical flux metric of a modal basis.
The documented leaf of contract section 3 that the step controller’s norm, the linear flow and every physical-energy statistic read, so that one W is shared by identity and never rebuilt. A coordinate change
c = T dtakes it toT.H @ W @ T; interference terms cannot be assigned unique positive per-channel energies, so coordinate intensities are not additive in a nonorthogonal basis.- Attributes:
- kind{‘identity’, ‘diagonal’, ‘dense’}
Which representation is stored.
"identity"owns no coefficient array;"diagonal"ownsdiagonal;"dense"ownsdense, is Hermitian at every interval, degree and frequency, and is what a declared unitary change of basis produces (dense_metric()). Which of them a backend executes is its own admission:numpy_reftakes the identity and a dense metric that is a unitary transform of one, and refuses a diagonal metric, which nothing builds.- omeganumpy.ndarray
The canonical coarse angular frequencies in rad/s, shape
(n_omega,), increasing and non-negative. A problem’s leaf carriesgrids.omegaitself, whose first bin is zero on a field grid (solprop.translate.problem.KernelProblem.validate()).- z_edgesnumpy.ndarray
Stored positions in m, shape
(n_edges,), increasing. The interval-indexed arrays are counted byinterval_count().- diagonalnumpy.ndarray or None
Positive dimensionless per-channel weights, shape
(n_intervals, n_deg, n_modes, n_omega); present exactly forkind="diagonal".- densenumpy.ndarray or None
Hermitian dimensionless metric, shape
(n_intervals, n_deg, n_modes, n_modes, n_omega); present exactly forkind="dense".- errornumpy.ndarray
Non-negative dimensionless bound on the stored representation, shape
(n_intervals, n_omega). Declared by the caller; nothing here infers it.- minimum_eigenvalue, maximum_eigenvaluenumpy.ndarray
Non-negative dimensionless extreme eigenvalues of W, shape
(n_intervals, n_omega), with the minimum never above the maximum. A minimum of zero is a semidefinite metric: it may be stored as flux data but certifies no positive error or noise norm.- gauge_id, certificate_idstr
Non-empty phase/coordinate identity and the identity of the certificate the bounds came from. Scalar identities, never arrays, enter resume identity.
- Raises:
- TranslationError
Naming the field that failed.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import ModalMetricTables >>> zeros = np.zeros((1, 3)) >>> metric = ModalMetricTables( ... "identity", np.linspace(1e15, 2e15, 3), np.array([0.0]), None, None, ... zeros, np.ones((1, 3)), np.ones((1, 3)), "lab", "orthonormal", ... ) >>> metric.kind, metric.n_omega ('identity', 3)
- dense: Complex[ndarray, 'n_intervals_dense n_deg_dense n_modes_dense_out n_modes_dense_in n_omega_dense'] | None#
- diagonal: Float[ndarray, 'n_intervals_diagonal n_deg_diagonal n_modes_diagonal n_omega_diagonal'] | None#
- error: Float[ndarray, 'n_intervals_metric_error n_omega_metric_error']#
- maximum_eigenvalue: Float[ndarray, 'n_intervals_metric_max n_omega_metric_max']#
- minimum_eigenvalue: Float[ndarray, 'n_intervals_metric_min n_omega_metric_min']#
- property n_modes: int[source]#
Return how many channels the metric describes.
- Returns:
- int
The channel count of whichever coefficient array is stored; 0 for
"identity", which owns none and takes its size from the state.
- property n_omega: int[source]#
Return how many coarse frequencies the metric is tabulated on.
- Returns:
- int
The length of
omega.
- omega: Float[ndarray, 'n_omega_metric']#
- z_edges: Float[ndarray, 'n_edges_metric']#
- solprop.translate.coupled.SECTION_TOL: float = 1e-12#
Relative coefficient tail a section’s z expansion is resolved to.
The same target
solprop.translate.chebyshev.DEFAULT_TOLsets for the cumulative operator, and for the same reason: below it the interpolant is resolving the round-off of its own samples. What is not resolved to it is refused rather than accepted with a larger error, because the residual bound this expansion feeds is the whole certificate of the frozen split.The samples’ own floor is well below it. The coherent wall’s diagonal
k - beta_11is formed from the two refractivities ((n_core - 1) - (n_eff - 1)), not as the difference of two wavenumbers that agree to five or six digits, which carried the absolute rounding ofk(about 5e-9 1/m, up to 5e-10 ofk - beta_11itself) and now carries 1e-14 of it at worst. Forced to degree 64 across the transition of the three- and six-channel qualification tapers and the TE/TM taper, the generator’s tail floors at 4e-14 relative (8e-15 to 4.0e-14), 25 times below this target, so a tail above it is unresolved structure and never round-off.
- solprop.translate.coupled.SectionMethod#
How one interval of a coupled flow is integrated.
"diagonal"is the existing cumulative commuting flow;"constant_schur"is the Schur matrix-function action of a constant coherent block;"frozen_residual"freezes the block inside the section and leaves its variation to the explicit source;"cf4"is the commutator-free fourth-order flow of the whole section generator, admitted bycf4_flow()behind its internal-growth and source-feedback guards.alias of
Literal[‘diagonal’, ‘constant_schur’, ‘frozen_residual’, ‘cf4’]
- solprop.translate.coupled.advisory_step(flow, scored=None)[source]#
Return the advisory step limit of every section and frequency, in m.
h_advisory = 0.9 / (K ||U_s|| ||U_s^-1||), withKthe interval upper bound on the norm of the whole explicit linear residual – which isCoupledFlowTables.residual_norm_bound– and the conditioning factor its interaction-picture similarity transform costs, which isCoupledFlowTables.inverse_condition_bound. Both rows are already across the seam, so this needs no row of its own.The factor 0.9 is a warning margin below the production tableau’s imaginary stability radius 0.997189, not an approved solver default and not a stability proof: a wall difference can have positive real eigenvalues, representing local growth relative to the frozen attenuating frame, and no positive step makes that residual alone contractive. In a lossy or nonnormal case this is advisory only and a growth certificate is still required (
solprop.translate.problem.validate_coupled_flow()says so in the warning).A section with an exactly zero residual – every constant one – returns infinity, because there is no explicit term to resolve. So does a bin outside scored: the explicit residual is applied only on the bins the step controller’s error norm scores, and the spectral collar beyond them is carried by the frozen flow alone, which has no stability limit.
- Parameters:
- flowCoupledFlowTables
The leaf.
- scorednumpy.ndarray of bool, optional
The bins the run applies the residual on, shape
(n_omega,): the error norm’sgrids.inband_mask.Nonetakes every bin.
- Returns:
- numpy.ndarray
Float64, shape
(n_intervals, n_omega), positive,infwhere the residual is exactly zero.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import advisory_step, constant_schur_flow >>> block = np.zeros((2, 2, 3), dtype=complex) >>> block[0, 0] = block[1, 1] = -1.0 >>> flow = constant_schur_flow( ... np.linspace(1e15, 2e15, 3), np.array([0.0, 1e-3]), block, ... dz_max=1e-4, condition_limit=10.0, gauge_id="lab", ... certificate_id="schur", ... ) >>> bool(np.all(np.isinf(advisory_step(flow)))) True
- solprop.translate.coupled.cf4_defect(flow, dz, kinks=())[source]#
Return the step-doubling estimate of every CF4 section’s local flow defect.
The source-feedback estimator, and what kind of statement it is. In the interaction picture
u = U bthe stage source isg = U^-1 N(U b), and a CF4 run uses an approximationVof every stage’sU. With||U - V|| <= delta, the source is perturbed by at most||V^-1|| delta (||U^-1|| ||N|| + L ||b||)–Lthe source’s Lipschitz constant on a ball containing both states – and the step’s end is moved bydelta ||b||directly. The direct term isO(h^5)per step; the feedback term enters the state through one more factorhand isO(h^6). The run is therefore at most fourth order in the flow’s own error, and the embedded Runge-Kutta pair, which sees only the difference of two stage combinations, does not see it at all. No Lipschitz constant of the material response is available on a containing state ball – Kerr, Raman and plasma each have their own, and none is enclosed by this package – so the rigorous envelope is not what is declared. What is declared is the asymptotic estimator: two half steps have one sixteenth of the leading error of a full one, so(16/15) ||V(h) - V(h/2)^2||estimates the full step’s local defect, qualified by observable refinement (measured against an independent DOP853 solution of the flow alone, and by the run’s own order). It is neither a bound nor valid near a kink, at the rounding floor, or outside the asymptotic regime – which nothing but this estimate polices: the internal-growth guard bounds growth from the Hermitian spread alone, so a lossless section admits any step, and far outside the regime the estimate underestimates (0.19 against a true 0.38 atΔβ h = 100). It is whatsolprop.translate.problem.validate_coupled_flow()compares with the controller’s tolerance.Each CF4 section is sampled with a step of length dz aligned with its start, its middle and its end, and the largest relative defect is kept, because the steepest part of a taper is where the defect is. A sampled step ends at the first of kinks after its start, and at the end of the run, because a step the driver takes does: adaptive stepping lands on every hard kink and a fixed schedule that straddles one is refused, and a CF4 step across a corner of the generator is not fourth order at all. Every other section returns zero.
- Parameters:
- flowCoupledFlowTables
The leaf, dense.
- dzfloat
The step to estimate over, m, positive.
- kinkssequence of float, optional
The run’s hard kinks in m (
linear.hard_kinks), which no step crosses.
- Returns:
- numpy.ndarray
The relative defect
||V(h) - U(h)||_2 / ||U(h)||_2, estimated, shape(n_intervals, n_omega).
- Raises:
- TranslationError
If the flow is factored or dz is not positive.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import cf4_defect, cf4_flow >>> block = np.zeros((1, 1, 2, 2, 1), dtype=complex) >>> block[0, 0, 1, 1] = -100.0 >>> flow = cf4_flow( ... np.array([1e15]), np.array([0.0, 1e-3]), block, None, dz_max=1e-3, ... condition_limit=10.0, representation_error=0.0, gauge_id="lab", ... certificate_id="cf4", ... ) >>> bool(cf4_defect(flow, 1e-3).max() < 1e-15) True
- solprop.translate.coupled.cf4_flow(omega, z_edges, generator, connection, *, dz_max, condition_limit, growth_limit=2.0, fallback=None, representation_error, gauge_id, certificate_id)[source]#
Build the CF4 coupled flow of a varying guide, with its two guards.
The certified alternative to
frozen_residual_flow(): nothing is frozen and nothing is left to an explicit residual. A step of lengthhfromzapplies the fourth-order commutator-free exponential of the whole section generatorL = G - C,U(h) = exp(h (a A_1 + b A_2)) exp(h (b A_1 + a A_2)),with
A_j = L(z + c_j h)at the Gauss nodesc = 1/2 -+ sqrt(3)/6anda, b = (3 -+ 2 sqrt(3))/12; the early-weighted factor acts first. Its local error isO(h^5)on a smooth section, so a run with it is at most fourth order globally, and the embedded Runge-Kutta pair does not see that error at all (cf4_defect()). Every section takes the same Chebyshev expansionfrozen_residual_flow()does; what differs is the certificate.The internal-growth guard, before admission.
ais negative, so a factor of a dissipative flow can grow: a block with eigenvalues 0 and -200/m over 1 m has internal factors of norm 1898 against an exact action of 0.96. With every Hermitian-part eigenvalue ofLover the reach of a step enclosed in[m_lo, m_hi](Weyl’s inequality on the stored expansion), the logarithmic norm bounds the product of the two forward factors byexp(h m_hi)– the largest the exact action can be – times the growth boundexp((2 sqrt(3) - 3)/6 dz_max (m_hi - m_lo)), and the four factors of a forward and inverse pair byexp((2/sqrt(3)) dz_max (m_hi - m_lo)), which is theinverse_condition_bounda CF4 section declares and the backend checks per step against the factors it actually forms. A section whose growth bound exceeds growth_limit, or whose conditioning exceeds condition_limit, is refused by name – or, only when fallback says so, takes"frozen_residual"instead, with a warning naming it.The rows of a CF4 section:
generator_erroris the expansion’s relative tail alone, there being no Schur action to diagnose;residual_norm_boundis zero, there being no explicit residual;inverse_condition_boundis the pair bound above. A fallback section carries exactly the rowsfrozen_residual_flow()gives it.- Parameters:
- omeganumpy.ndarray
The canonical coarse angular frequencies in rad/s, increasing.
- z_edgesnumpy.ndarray
The section edges in m, shape
(n_intervals + 1,), covering the run.- generatornumpy.ndarray
Chebyshev coefficients along z of the complete internal-convention generator in 1/m, shape
(n_intervals, n_deg, n_modes, n_modes, n_omega).- connectionnumpy.ndarray or None
The taper connection’s coefficients in the same layout, or
None.- dz_maxfloat
Largest step the run may take, m, positive.
- condition_limitfloat
Largest admitted conditioning of a step’s flow action, at least one.
- growth_limitfloat, optional
Largest admitted growth bound of a CF4 section, at least one. Default
CF4_GROWTH_LIMIT.- fallback{None, ‘frozen_residual’}, optional
What a section the guards reject becomes.
None, the default, refuses it.- representation_errorfloat
The relative Chebyshev tail of the expansions, non-negative.
- gauge_idstr
The basis’s phase and coordinate identity.
- certificate_idstr
Identity of the certificate the bounds came from.
- Returns:
- CoupledFlowTables
The validated leaf,
"cf4"on every admitted section.
- Raises:
- TranslationError
If a limit or the fallback is out of range, on everything
frozen_residual_flow()refuses, if a section fails a guard with no fallback declared, or if a fallback section fails the frozen conditioning.
- Warns:
- solprop.exceptions.SolpropWarning
Naming every section that fell back, the guard that rejected it and its bound.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import cf4_flow >>> block = np.zeros((2, 1, 2, 2, 3), dtype=complex) >>> block[:, 0, 0, 0] = block[:, 0, 1, 1] = -1.0 >>> flow = cf4_flow( ... np.linspace(1e15, 2e15, 3), np.array([0.0, 5e-4, 1e-3]), block, None, ... dz_max=1e-4, condition_limit=10.0, representation_error=0.0, ... gauge_id="lab", certificate_id="cf4", ... ) >>> flow.section_methods, float(flow.inverse_condition_bound.max()) (('cf4', 'cf4'), 1.0)
- solprop.translate.coupled.cf4_growth_bound(flow)[source]#
Return the internal growth bound of every section, dimensionless.
exp((2 sqrt(3) - 3)/6 dz_max (m_hi - m_lo))for a"cf4"section, read back from the conditioning row it declares,exp((2/sqrt(3)) dz_max (m_hi - m_lo)): the growth bound is that row to the power(2 - sqrt(3))/4, so it needs no row of its own (cf4_flow()). Every other section returns one, having no internal factors to grow.- Parameters:
- flowCoupledFlowTables
The leaf.
- Returns:
- numpy.ndarray
Float64, shape
(n_intervals, n_omega), at least one.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import cf4_flow, cf4_growth_bound >>> block = np.zeros((1, 1, 2, 2, 1), dtype=complex) >>> block[0, 0, 1, 1] = -100.0 >>> flow = cf4_flow( ... np.array([1e15]), np.array([0.0, 1e-3]), block, None, dz_max=1e-3, ... condition_limit=10.0, representation_error=0.0, gauge_id="lab", ... certificate_id="cf4", ... ) >>> f"{float(cf4_growth_bound(flow)[0, 0]):.6f}" '1.007765'
- solprop.translate.coupled.constant_schur_flow(omega, z_edges, generator, *, dz_max, condition_limit, wall_vectors=None, wall_weights=None, gauge_id, certificate_id)[source]#
Build the coupled flow of one constant section, with its certificate.
The coupled flow at constant geometry: one section whose complete generator is frozen – exactly, because it does not vary – and integrated by a Schur matrix-function action, with the explicit residual identically zero. The dense generator is stored, and the rank-one wall pair beside it when the caller has one, so that a reader can see which part of the block is the wall without factoring it back out.
The generator is in the internal FFT convention, like every other array of the contract, and carries only what the diagonal cumulative flow does not: the shared scalar carrier of the section stays in
linear.phi, where it is integrated analytically and commutes with the whole block.- Parameters:
- omeganumpy.ndarray
The canonical coarse angular frequencies in rad/s, increasing.
- z_edgesnumpy.ndarray
The section’s two edges in m, covering the run.
- generatornumpy.ndarray
The constant internal-convention generator in 1/m, complex, shape
(n_modes, n_modes, n_omega), exactly zero on every bin the model was not evaluated at.- dz_maxfloat
Largest step the run may take, m, positive: what the conditioning bound is declared over.
- condition_limitfloat
Largest admitted conditioning, dimensionless and at least one.
- wall_vectorsnumpy.ndarray or None, optional
The rank-one wall vector
h, dimensionless, shape(n_modes,).- wall_weightsnumpy.ndarray or None, optional
Its weight in 1/m, complex, shape
(n_omega,), supplied with the vector. The pair multiplies out to the generator’s own 1/m: the vector carries no dimension and the weight carries the whole of it.- gauge_idstr
The basis’s phase and coordinate identity.
- certificate_idstr
Identity of the certificate the bounds came from.
- Returns:
- CoupledFlowTables
The validated leaf,
section_methods == ("constant_schur",).
- Raises:
- TranslationError
If the generator varies in shape from the axes, if the wall pair does not reconstruct the generator’s off-diagonal, or if the conditioning bound exceeds condition_limit at any frequency.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import constant_schur_flow >>> block = np.zeros((2, 2, 3), dtype=complex) >>> block[0, 0] = block[1, 1] = -1.0 >>> flow = constant_schur_flow( ... np.linspace(1e15, 2e15, 3), np.array([0.0, 1e-3]), block, ... dz_max=1e-4, condition_limit=10.0, gauge_id="lab", ... certificate_id="schur", ... ) >>> flow.section_methods, float(flow.residual_norm_bound.max()) (('constant_schur',), 0.0)
- solprop.translate.coupled.dense_metric(omega, z_edges, sampled, *, defect, gauge_id, certificate_id)[source]#
Build the dense metric of a basis from its own sampled flux Gram.
The companion of
orthonormal_metric()for a basis whose flux metric is no longer the identity in any gauge it could be written in. The caller suppliesW(omega)measured on the basis itself – the Hermitian Poynting pairingsolprop.modes.vector.flux_product()integrates – and this stores it as the documented leaf of contract section 3, with the eigenvalue certificate a controller norm and a noise draw need.Why it is genuinely dense. Under a declared unitary change of basis
Ua reference whose sampled Gram isI + deltabecomesI + U.H delta U. The exact identity is invariant, but the measured defect is not: the off-diagonal ofU.H delta Uis not the off-diagonal ofdelta, and it is the measured object a run carries. Storing a literal identity here instead would be a decorated identity rather than a declaration of what the basis is.The metric is constant along z in this constructor: one interval carrying one Chebyshev coefficient, which is degree zero, because the general modal route propagates constant geometry and its reference is a snapshot.
- Parameters:
- omeganumpy.ndarray
The canonical coarse angular frequencies in rad/s, increasing, shape
(n_omega,).- z_edgesnumpy.ndarray
Stored positions in m, increasing, covering the run.
- samplednumpy.ndarray
The Hermitian
W(omega), shape(n_modes, n_modes, n_omega), dimensionless and positive definite at every frequency.- defectfloat
The measured
norm(W - I, 2)of the reference basis this one was built from, non-negative and dimensionless: the table’sgram_defect, which a unitary transform leaves unchanged in the spectral norm.- gauge_idstr
The basis’s composed phase and coordinate identity.
- certificate_idstr
Identity of the certificate defect came from.
- Returns:
- ModalMetricTables
The validated dense metric, with the extreme eigenvalues of the stored block per frequency.
- Raises:
- TranslationError
If the sampled metric is not a square positive-definite Hermitian block on the given frequencies.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import dense_metric >>> sampled = np.broadcast_to(np.eye(2, dtype=complex)[:, :, None], (2, 2, 3)) >>> metric = dense_metric( ... np.linspace(1e15, 2e15, 3), np.array([0.0, 1.0]), sampled, ... defect=1e-12, gauge_id="lab+circular(0,1)", certificate_id="sampled-gram", ... ) >>> metric.kind, metric.n_modes ('dense', 2)
- solprop.translate.coupled.eigenbasis_limit(n_modes)[source]#
Return the largest eigenbasis condition the compiled flow actions accept.
The compiled backends form a flow action
Z exp(l T) Z^Hfrom the eigenbasis of the Schur factor,W exp(l Lambda) Y(schur_eigenbasis()), where its rounding is bounded bykappa (n + 11 + 2 a) urelative to the action, withkappa = || |W| ||_2 || |Y| ||_2anda = max |l lambda|. The reference’s scaling-and-squaring is bounded by16 u + 2 sqrt(2) n (n + 2) uat its smallest – no squaring, a vanishing exponent: the sixteen-term series and the two products of the back-transform – and grows with the step from there. The limit is thekappaat which the two routes’ vanishing-step rounding terms meet – a heuristic gate, not a bound comparison at every step length (the eigenbasis bound also carries the measured||WY - I||and a drift that grows with the step); measured, the eigenbasis route is at least as accurate as the reference’s on every case tried; a bin beyond it (a nearly degenerate pair with a nonnormal coupling, wherekappagrows as the inverse of the separation) is formed by the compiled scaling-and-squaring instead. The bounds are derived intests/flow_parity.pyanddocs/dev/backends.mdsection 3.9.- Parameters:
- n_modesint
Channels of the flow’s block, positive.
- Returns:
- float
The limit, dimensionless: 2.04 for one channel, rising with
n; a one-channel block haskappa = 1exactly.
Examples
>>> from solprop.translate.coupled import eigenbasis_limit >>> round(eigenbasis_limit(3), 3) 4.173
- solprop.translate.coupled.flow_edge_slack(flow)[source]#
Return the tolerance a position is matched to a section edge within.
A step schedule lands on a declared kink by summing its steps, so it arrives within a few ulp of the total span rather than on it exactly;
chebyshev._EDGE_SLACK_FRACTIONof the span – the same fractionsolprop.translate.chebyshev.PiecewiseChebyshevmatches its own breakpoints to – makes the section a stage is evaluated on depend on the schedule’s intent rather than on its last bits.It is also what
KernelProblem._check_scheduleofsolprop.translate.problemholds a fixed schedule to beside a coupled flow. That check’s own window is wider (1e-9of the run, which is 1e-9 m for anything under a metre), and a schedule edge between the two lands a step anchored in the closing section but running past the corner, against a frozen block that is not the one its stages read: fifth order collapses to first with nothing said. The two windows must therefore be one quantity, and a schedule that falls between them is refused rather than silently integrated.- Parameters:
- flowCoupledFlowTables
The leaf.
- Returns:
- float
The tolerance in m, positive.
- solprop.translate.coupled.flow_section(flow, z)[source]#
Return the index of the section a position lies in.
- Parameters:
- flowCoupledFlowTables
The leaf.
- zfloat
Position in m.
- Returns:
- int
The section index, clamped to the ends: a position a rounding outside the span belongs to the nearest section, as a step landing on the last plane is. A position within
1e-10of the span of an interior edge belongs to the section starting there, because a schedule that lands on a kink arrives a few ulp short of it as often as a few ulp past, and the two must not name different sections for the same step.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import constant_schur_flow, flow_section >>> block = np.zeros((2, 2, 3), dtype=complex) >>> block[0, 0] = block[1, 1] = -1.0 >>> flow = constant_schur_flow( ... np.linspace(1e15, 2e15, 3), np.array([0.0, 1e-3]), block, ... dz_max=1e-4, condition_limit=10.0, gauge_id="lab", ... certificate_id="schur", ... ) >>> flow_section(flow, 2e-3) 0
- solprop.translate.coupled.flow_weights(flow, z, section=None)[source]#
Return the weights that combine a coupled flow’s coefficients at one position.
The stage-bundle half of the seam’s evaluation form, for the coupled flow’s own
(n_intervals, n_deg)coefficient block: a backend evaluatesG(z) = sum_j w_j terms_jover the flattened block, exactly as it evaluatesdPhioversolprop.translate.stage.StageTerms.phi(docs/dev/kernel-contract.mdsection 1). Every weight outside the section containing z is exactly zero, so no section’s expansion is ever extrapolated, and the connection is combined over the same weights because it shares the axes.Which section a position on an edge belongs to is the step’s to say. A section edge is a kink: the declared taper corner the sections were placed on, where
a'jumps and the connection with it. A stage that lands exactly on the end of its own step’s section must read the closing side of that corner, not the opening one, or one stage of that step evaluates a source discontinuously larger than the rest and the whole run drops to first order – measured, before the tie was broken this way, as a global error ofhtimes 1.0e2 per metre on the qualification taper. section is therefore the step’s own, and it wins whenever the position lies inside its closed interval; a position genuinely outside it – which only a step spanning an edge produces, and the driver lands on every hard kink – falls back to its own section rather than extrapolating a Chebyshev expansion past its end, which diverges asexp(2 n sqrt(eps)). That fallback takes the section the stage closes in rather than the one that opens at it, for the same reason: a stage that ran past its step’s section arrived from below, and the residual it is measured against is the one-sided limit on the side it came from.- Parameters:
- flowCoupledFlowTables
The leaf.
- zfloat
Position in m.
- sectionint or None, optional
The step’s own section, from
solprop.translate.stage.StageScalars.flow_section.Nonetakes the section containing z, which is what a caller with no step to speak of wants.
- Returns:
- numpy.ndarray
Float64, shape
(n_intervals * n_deg,).
Examples
>>> import numpy as np >>> from solprop.translate.coupled import constant_schur_flow, flow_weights >>> block = np.zeros((2, 2, 3), dtype=complex) >>> block[0, 0] = block[1, 1] = -1.0 >>> flow = constant_schur_flow( ... np.linspace(1e15, 2e15, 3), np.array([0.0, 1e-3]), block, ... dz_max=1e-4, condition_limit=10.0, gauge_id="lab", ... certificate_id="schur", ... ) >>> flow_weights(flow, 5e-4).tolist() [1.0]
- solprop.translate.coupled.frozen_generator(flow, index)[source]#
Return the generator one section of a coupled flow is frozen at.
The frozen point is the section’s midpoint, and it is read out of the stored expansion rather than carried beside it: the translation layer’s certificate and the backend’s Schur factors are then the same matrix to the last bit, because they come from the same coefficients through the same evaluation. At the midpoint
x = 0, so the weights areT_k(0)– one, zero, minus one, zero, … – and a section of one degree returns its only coefficient unchanged, which keeps the constant-Schur flow bitwise the stored block.- Parameters:
- flowCoupledFlowTables
The leaf.
- indexint
Which section, in
[0, n_intervals).
- Returns:
- numpy.ndarray
The frozen generator in 1/m, shape
(n_modes, n_modes, n_omega).
- Raises:
- TranslationError
If the flow carries the factored representation, which has no dense generator to freeze.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import constant_schur_flow, frozen_generator >>> block = np.zeros((2, 2, 3), dtype=complex) >>> block[0, 0] = block[1, 1] = -1.0 >>> flow = constant_schur_flow( ... np.linspace(1e15, 2e15, 3), np.array([0.0, 1e-3]), block, ... dz_max=1e-4, condition_limit=10.0, gauge_id="lab", ... certificate_id="schur", ... ) >>> bool(np.array_equal(frozen_generator(flow, 0), block)) True
- solprop.translate.coupled.frozen_residual_flow(omega, z_edges, generator, connection, *, dz_max, condition_limit, representation_error, gauge_id, certificate_id)[source]#
Build the certified coupled flow of a varying guide, frozen section by section.
The coupled flow at varying geometry. Each section carries the complete generator as a Chebyshev expansion along z; the block frozen inside the exponential is that expansion at the section midpoint (
frozen_generator()), and everything else –R_s(z) = D(z) - D(z_s) + H(z) - H(z_s) - C(z)– is the explicit residual the interaction-picture Runge-Kutta source carries. With ``R`` evaluated exactly, freezing is a rearrangement of the original equation, not a second-order approximation; section refinement reduces the residual’s stiffness and the inverse amplification and is not permission to discard it.
The residual row is a true enclosure over each whole section rather than a sampled maximum: with
R_s = sum_k r_k T_kand|T_k| <= 1,sum_k ||r_k||_2bounds it at every position, extrema no sample saw included. The interpolant’s own error is the representation_error the caller measured, which joins the Schur diagnostic ingenerator_error.- Parameters:
- omeganumpy.ndarray
The canonical coarse angular frequencies in rad/s, increasing.
- z_edgesnumpy.ndarray
The section edges in m, shape
(n_intervals + 1,), covering the run.- generatornumpy.ndarray
Chebyshev coefficients along z of the complete internal-convention generator in 1/m, shape
(n_intervals, n_deg, n_modes, n_modes, n_omega), exactly zero on every bin the model was not evaluated at.- connectionnumpy.ndarray or None
The taper connection’s coefficients in the same layout and units, or
Nonefor a guide whose radius does not move.- dz_maxfloat
Largest step the run may take, m, positive: what the conditioning bound is declared over.
- condition_limitfloat
Largest admitted conditioning, dimensionless and at least one.
- representation_errorfloat
The relative Chebyshev tail of the expansions, dimensionless and non-negative, as
section_expansion()measured it.- gauge_idstr
The basis’s phase and coordinate identity.
- certificate_idstr
Identity of the certificate the bounds came from.
- Returns:
- CoupledFlowTables
The validated leaf,
section_methods == ("frozen_residual",) * n_intervals.
- Raises:
- TranslationError
If the arrays disagree with the axes, if representation_error is negative, or if the conditioning bound exceeds condition_limit at any frequency.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import frozen_residual_flow >>> block = np.zeros((2, 1, 2, 2, 3), dtype=complex) >>> block[:, 0, 0, 0] = block[:, 0, 1, 1] = -1.0 >>> flow = frozen_residual_flow( ... np.linspace(1e15, 2e15, 3), np.array([0.0, 5e-4, 1e-3]), block, None, ... dz_max=1e-4, condition_limit=10.0, representation_error=0.0, ... gauge_id="lab", certificate_id="frozen-residual", ... ) >>> flow.section_methods, float(flow.residual_norm_bound.max()) (('frozen_residual', 'frozen_residual'), 0.0)
- solprop.translate.coupled.interval_count(edges)[source]#
Return how many z intervals a set of stored positions declares.
- Parameters:
- edgesint
Number of stored positions, at least one.
- Returns:
- int
edges - 1for a path, and 1 for a single snapshot, whose one interval is the position it was taken at. A record’s interval-indexed arrays – the error rows, the coefficient representations – use this count, while its dense arrays are indexed by the positions themselves.
Examples
>>> from solprop.translate.coupled import interval_count >>> interval_count(1), interval_count(4) (1, 3)
- solprop.translate.coupled.metric_at(metric, z)[source]#
Evaluate a basis metric at one statistics or observation plane.
The one arithmetic path from the stored leaf to the
Wa statistic is handed, so that the run-time reduction and a later check of it read the same numbers.Noneis the identity and is returned asNonerather than as an array of ones: the statistics takemetric=Noneas their fast, bitwise-unchanged path, and building an identity here would make every unweighted run pay for a matrix product it does not need.- Parameters:
- metricModalMetricTables or None
The leaf, or
Nonefor a problem that carries none.- zfloat
Position in m inside the metric’s own span.
- Returns:
- numpy.ndarray or None
W(omega), shape(n_modes, n_modes, n_omega), dimensionless;Nonefor an identity metric or no metric at all.
- Raises:
- TranslationError
If the metric is diagonal, which no route builds and nothing executes.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import dense_metric, metric_at >>> sampled = np.broadcast_to(np.eye(2, dtype=complex)[:, :, None], (2, 2, 3)) >>> metric = dense_metric( ... np.linspace(1e15, 2e15, 3), np.array([0.0, 1.0]), sampled, ... defect=0.0, gauge_id="lab+circular(0,1)", certificate_id="sampled-gram", ... ) >>> metric_at(metric, 0.5).shape (2, 2, 3) >>> metric_at(None, 0.5) is None True
- solprop.translate.coupled.orthonormal_metric(omega, z_edges, *, defect, gauge_id, certificate_id)[source]#
Build the identity metric of a basis certified orthonormal in true power.
A Poynting-normalised bound basis whose sampled Gram passed the construction gate of
solprop.translate.modal_tables.DenseModalTablehasW = Ito within that gate’s budget, so the metric owns no coefficient array at all and the measured defect is what the error row carries. The extreme eigenvalues are one: a certified positive metric, which is what the noise draw and the controller norm need.- Parameters:
- omeganumpy.ndarray
The canonical coarse angular frequencies in rad/s, increasing.
- z_edgesnumpy.ndarray
Stored positions in m, increasing, covering the run.
- defectfloat
The measured
norm(W - I, 2)of the basis, non-negative and dimensionless: the table’sgram_defect.- gauge_idstr
The basis’s phase and coordinate identity.
- certificate_idstr
Identity of the certificate the defect came from.
- Returns:
- ModalMetricTables
The validated identity metric.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import orthonormal_metric >>> metric = orthonormal_metric( ... np.linspace(0.0, 2e15, 3), np.array([0.0, 1.0]), defect=1e-12, ... gauge_id="lab", certificate_id="sampled-gram", ... ) >>> metric.kind, metric.diagonal is None ('identity', True)
- solprop.translate.coupled.residual_truncation(x)[source]#
Return the frozen-residual truncation defect of one step, dimensionless.
||R(h R_s) - exp(h R_s)|| <= x^6/3600 + exp(x) x^7/5040withx = h K, valid in any matrix norm and without an eigenvector assumption. The first term is|1/600 - 1/720| x^6, the difference between the production DOPRI5 tableau’s own sixth-order coefficient1/600and the exponential’s1/720; the second bounds the exponential tail from the seventh order.It is a local frozen defect, not a bound for a time-varying stage sequence, and it is charged to the linear share of the error budget, with the Schur action and the explicit-linear integration.
- Parameters:
- xfloat
h K, dimensionless and non-negative.
- Returns:
- float
The bound, dimensionless;
infwhere the exponential tail overflows a double, which a step so far above the advisory one reaches (a declareddz_maxof half a capillary, for instance) and which the advisory warning then reports rather than failing on.
- Raises:
- TranslationError
If x is negative or not finite.
Examples
>>> from solprop.translate.coupled import residual_truncation >>> f"{residual_truncation(0.1):.4e}" '2.9971e-10' >>> residual_truncation(0.0) 0.0 >>> residual_truncation(1e3) inf
- solprop.translate.coupled.schur_blocks(generator)[source]#
Return the complex Schur factors of a generator, one per frequency.
G = Z T Z^HwithZunitary andTupper triangular, which is the decomposition a matrix-function action of a nonnormal generator is entitled to: an eigenvector basis of a wall block near a degeneracy is arbitrarily ill-conditioned, and the Schur factor is not. A frequency whose block is exactly zero – an out-of-band bin the wall formula was never evaluated at – is not decomposed at all: its factors are the identity and zero, which is what the decomposition would give and costs nothing.- Parameters:
- generatornumpy.ndarray
The generator in 1/m, complex, shape
(n_modes, n_modes, n_omega).
- Returns:
- tuple of numpy.ndarray
ZandT, each complex of shape(n_omega, n_modes, n_modes): the frequency axis leads, because that is the axis a batched action broadcasts over.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import schur_blocks >>> block = np.zeros((2, 2, 3), dtype=complex) >>> block[0, 1, 1] = 3.0 >>> vectors, triangular = schur_blocks(block) >>> vectors.shape, float(abs(triangular[1]).max()) ((3, 2, 2), 3.0)
- solprop.translate.coupled.schur_eigenbasis(vectors, triangular)[source]#
Return the eigenbasis of each Schur factor, and where it is well conditioned.
With
G = Z T Z^HandTupper triangular with distinct diagonallambda,T = V Lambda V^-1withVunit upper triangular, so every action of the flow over a lengthlisW exp(l Lambda) YwithW = Z VandY = V^-1 Z^H:nexponentials and onen^3product, shared by the forward and inverse actions and by every stage length of a step.Vis formed column by column by back substitution,V_ij = sum_k T_ik V_kj / (lambda_j - lambda_i), and its inverse by the division-free unit triangular recurrence, each sum taken from zero in increasingk.Where two diagonal entries nearly coincide with a coupling between them,
Vis ill-conditioned and the route loses accuracy as the inverse of the separation; where they coincide exactly it does not exist. A bin is therefore marked for this route only whereVis finite and|| |W| ||_2 || |Y| ||_2is withineigenbasis_limit(); the others are formed by scaling and squaring. TheirWandYare returned as zeros.- Parameters:
- vectorsnumpy.ndarray
The unitary Schur factors
Z, complex, shape(n_omega, n_modes, n_modes), asschur_blocks()returns them.- triangularnumpy.ndarray
The triangular factors
T, 1/m, of the same shape.
- Returns:
- tuple of numpy.ndarray
WandY, complex of the input’s shape; the eigenvalueslambda, 1/m, shape(n_omega, n_modes); and the boolean route, shape(n_omega,), true where the eigenbasis is used.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import schur_eigenbasis >>> vectors = np.eye(2, dtype=complex)[None] >>> triangular = np.array([[[-1.0, 0.1], [0.0, -2.0]]], dtype=complex) >>> left, right, eigenvalues, route = schur_eigenbasis(vectors, triangular) >>> bool(route[0]), bool(np.allclose(left[0] @ right[0], np.eye(2))) (True, True)
- solprop.translate.coupled.section_expansion(sample, z_edges, *, tol=1e-12, max_degree=32, name='the section expansion')[source]#
Expand an array-valued function of z in Chebyshev coefficients per section.
The fixed-section counterpart of
solprop.translate.chebyshev.build(): the edges are given – they are the flow’s sections, which already land on every declared kink – so nothing is split and nothing is guessed, and the only search is the degree. One degree is resolved for every section, because the leaf’sn_degaxis is rectangular, and it is the largest any section needed.The expansion is what makes the explicit residual a bounded quantity rather than a sampled one:
|T_k| <= 1, sosum_k ||c_k||encloses the function over the whole section including the extrema no sample sees (frozen_residual_flow()).- Parameters:
- samplecallable
Takes positions in m, shape
(n_z,), and returns the values with the position axis first, shape(n_z, *value_shape).- z_edgesnumpy.ndarray
The section edges in m, shape
(n_intervals + 1,), strictly increasing.- tolfloat, optional
Relative coefficient tail to resolve to. Default
SECTION_TOL.- max_degreefloat, optional
Highest degree tried. Default
MAX_SECTION_DEGREE.- namestr, optional
What the refusal calls the quantity.
- Returns:
- tuple
The coefficients, shape
(n_intervals, n_deg, *value_shape), and the worst relative tail actually reached, dimensionless.
- Raises:
- TranslationError
If a section’s tail is still above tol at max_degree.
Examples
>>> import numpy as np >>> from solprop.translate.coupled import section_expansion >>> coefficients, tail = section_expansion( ... lambda z: np.exp(-z)[:, None], np.array([0.0, 0.5, 1.0]) ... ) >>> coefficients.shape[0], bool(tail < 1e-12) (2, True)
- solprop.translate.coupled.taper_identity(flow, *, channels, active, scored=None, n_components=1)[source]#
Return the schema-7 modal identity of a capillary taper’s coupling.
A multimode capillary taper without a wall carries the taper coupling in a coupled flow, and a file must say so: a resume against the adiabatic route of the same geometry is a different equation. The record is
wall_identity()’s with the wall’s own entries – its validity ratios, its radius and its impedance – absent, because no wall block was built;flow.modelis"taper coupling"and the certificate issolprop.translate.problem.TAPER_FLOW_CERTIFICATE. A new key entry and not a new schema, as forwall_identity(). The basis is still the orthogonal"capillary HE_1m"set, so the ordinary accessors stay admitted.- Parameters:
- flowCoupledFlowTables
The leaf the run carries.
- channelssequence of solprop.modes.protocol.ChannelLabel
The channel labels in state order.
- activenumpy.ndarray of bool
The run’s active band, whose ends are recorded.
- scorednumpy.ndarray of bool, optional
The error norm’s bins, over which the recorded advisory step is taken (
advisory_step());Nonetakes every bin.- n_componentsint, optional
The transverse components the synthesis resolves.
- Returns:
- collections.abc.Mapping
The
modalrecord ofsolprop.output.schema.RunMetadata.
Examples
>>> import numpy as np >>> from solprop.modes.protocol import ChannelLabel >>> from solprop.translate.coupled import frozen_residual_flow, taper_identity >>> omega = np.array([2.3e15, 2.4e15]) >>> block = np.zeros((1, 1, 2, 2, 2), dtype=complex) >>> block[0, 0, 1, 1] = -1j * 1e3 >>> flow = frozen_residual_flow( ... omega, np.array([0.0, 1e-3]), block, None, dz_max=1e-5, ... condition_limit=100.0, representation_error=0.0, ... gauge_id="capillary HE_1m", certificate_id="taper-frozen-residual", ... ) >>> labels = [ChannelLabel("HE", 1, m, None, None, f"HE1{m}") for m in (1, 2)] >>> record = taper_identity(flow, channels=labels, active=np.ones(2, bool)) >>> record["flow"]["model"], record["basis"] ('taper coupling', 'capillary HE_1m')
- solprop.translate.coupled.wall_identity(flow, limits, *, channels, impedance='mean', growth_limit=None, fallback=None, dz_max=None, kinks=(), scored=None, n_components=1)[source]#
Return the schema-7 modal identity of a capillary carrying a coherent wall.
A new key entry, not a new schema:
modal.flowjoins the identity the general modal route writes, asmodal.guide.sampling.basisdoes, without moving output schema 7. A file written without aflowentry keeps the key it was stamped with.The identity is what a resume compares, so no array enters it and every measured float is quantised (
solprop.modes.material.quantised_measurement()) against the last bits of the BLAS that produced it. What is recorded of the block is what makes it the block it is: the section methods and how many of them there are, the degree of their expansions along z, whether a taper connection is carried, the two identity strings, the three validity ratios, the band they were taken over, the three certificate rows reduced to their extremes and the advisory step they imply (advisory_step()). The radius and the ratios are the worst section’s (solprop.modes.capillary.worst_wall_limits()), which for a constant guide is the only one there is, taken over the error norm’s scored band as the run’s admission takes them.modal.basisis"capillary HE_1m", which is howsolprop.output.capability.capability_of()tells this identity from a general guide’s: the coherent wall does not change the basis, which is the same orthogonalHE_1mset it always was, only the flow through it.- Parameters:
- flowCoupledFlowTables
The leaf the run carries.
- limitssolprop.modes.capillary.WallLimits
The validity ratios the block was admitted under.
- channelssequence of solprop.modes.protocol.ChannelLabel
The channel labels in state order.
- impedancestr, optional
Which wall the run declared. A
"te_tm"block writes the model"te_tm impedance coherent wall"and the basis"capillary azimuthal order"rather than"capillary HE_1m", because its channels are two radial families of one azimuthal order and not oneHE_1mset; it is still an orthogonal capillary mode set, sosolprop.output.capability.capability_of()still reads it as the ordinary modal basis and the ordinary accessors stay admitted. A resume across a changed wall impedance is refused by this field.- growth_limitfloat or None, optional
The CF4 growth limit the flow was admitted under; required, with dz_max, exactly when a section is
"cf4".- fallbackstr or None, optional
The declared fallback of a CF4 flow, recorded as declared.
- dz_maxfloat or None, optional
The largest step, m, over which the CF4 defect is estimated.
- kinkssequence of float, optional
The run’s hard kinks in m, at which a sampled CF4 step ends (
cf4_defect()).- scorednumpy.ndarray of bool, optional
The error norm’s bins, over which the recorded advisory step is taken, as the run’s warning takes it (
advisory_step());Nonetakes every bin.- n_componentsint, optional
The transverse components the synthesis resolves, 1 or 2: two for a supplied
TE_0m,TM_0morEHset, one for the capillary’s ownHE_1mset.
- Returns:
- collections.abc.Mapping
The
modalrecord ofsolprop.output.schema.RunMetadata. A flow with a"cf4"section adds aflow.cf4entry besideadvisory_step– a new key entry, asflowitself is, and absent from every other flow, whose record is therefore unchanged: the largest internal growth bound (cf4_growth_bound()), the limit it was held to, the declared fallback, and the largest step-doubling defect estimate at dz_max (cf4_defect()), named as the asymptotic estimator it is.
- Raises:
- TranslationError
If a CF4 flow is recorded without its growth limit or its step.
Examples
>>> import numpy as np >>> from solprop.modes import MarcatiliMode, wall_operator >>> from solprop.translate.coupled import constant_schur_flow, wall_identity >>> modes = [MarcatiliMode(125e-6, "He", 1.0, m=index) for index in (1, 2)] >>> omega = np.array([2.3e15, 2.4e15]) >>> block = wall_operator(modes, omega) >>> flow = constant_schur_flow( ... omega, np.array([0.0, 1e-3]), np.conj(block.generator), dz_max=1e-5, ... condition_limit=100.0, gauge_id="capillary HE_1m", ... certificate_id="constant-schur", ... ) >>> record = wall_identity( ... flow, block.limits, channels=[mode.channel_label for mode in modes] ... ) >>> record["basis"], record["flow"]["section_methods"] ('capillary HE_1m', ['constant_schur'])