solprop.translate.modal_path#

Sample a guide whose geometry moves along z, and expand what a run reads of it.

A capillary’s modes are J_0(u r/a)/a, a pure function of r/a, so a tapered capillary’s whole transform is its entrance transform times one scalar. A solid guide’s modes change shape along a taper – a step-index or graded-index mode moves with its normalised frequency – so the general route’s synthesis S and projection Q move with z and every nonlinear overlap follows them. This module is where that motion is sampled and expanded.

The node rule moves with the geometry. Each sampled position is solved on the provider’s own rule at that position, asked for at one fixed frequency and one fixed resolution (path_rule()): its panel edges sit on the material interfaces there, so every solve is integrated exactly (a rule whose panel edge has left a moving interface is wrong at first order in the displacement). Node q of the rule at z is a point x_q(z) that moves smoothly with the interfaces, and its material region does not change, so the field a map stores at node q is a smooth function of z inside a section and the per-node region index the seam carries is the same at every position. That is what lets the maps be expanded along z at all: on a rule frozen at one position a node the interface sweeps over changes material half way along, and the field there has a kink or a jump in z that no expansion in z resolves. The connection’s derivative is a different quantity – the Eulerian <psi_m | d_z psi_n> at fixed transverse position – and it keeps the union-split rule of solprop.modes.connection.connection_nodes(), which is exactly what that derivative needs; the two rules describe the same physical modes and differ only in where they integrate them.

The sampling is per section, at the Chebyshev-Lobatto points of a degree the tables’ own accuracy check demands. The sections are the provider’s declared kink intervals, optionally subdivided (path_sections()); a section never spans a kink, so everything sampled inside it is smooth. The degree is doubled until the expansion reproduces a fresh solve at the positions the doubling adds – the held-out points – to within the budget, which by default is the frequency interpolation’s own measured error, so that sampling in z is never the coarser of the two (sample_modal_path()).

The gauge is the provider’s own. Each position is solved and kept in the gauge the provider returns, with no tracking alignment, because the connection (solprop.modes.connection.band_connection()) is measured in that gauge: its stencil is aligned to the provider’s own centre solve. A provider whose own gauge is not continuous along the path – a sign that flips between two positions or two frequencies, a phase that drifts – would put a gauge connection in the maps that the measured connection does not carry, so it is refused by name (path_snapshot()).

Module Attributes

INITIAL_PATH_DEGREE

Chebyshev degree along z a path's accuracy search starts at, per section.

MAX_PATH_DEGREE

Highest Chebyshev degree along z a path's accuracy search tries per section.

PATH_RTOL_FLOOR

Smallest held-out error along z a path's accuracy search is asked to reach.

GAUGE_PHASE_ATOL

Largest phase, rad, between two neighbouring samples' aligned overlap.

Functions

in_frequency(knots, values, omega, inside)

Return knot values interpolated onto a run's bins, zero outside the band.

path_connections(path, provider, *[, ...])

Measure the moving-basis connection at every sampled position of a path.

path_rule(provider, rule, z)

Return the provider's own rule at z, asked for exactly as rule was.

path_sections(provider[, sections])

Return the section edges of a moving provider's path, in m.

path_snapshot(provider, omega, z, nodes, *)

Solve one position of a path over the band, in the provider's own gauge.

path_transform_tables(path, omega, *, z_end, ...)

Build the flat seam record of a moving guide, its maps expanded along z.

sample_modal_path(provider, omega, rule, ...)

Sample a moving guide along its sections, at the degree its accuracy demands.

section_coefficients(values)

Return the Chebyshev coefficients along z of values sampled per section.

transform_section(tables, z)

Return the section of a record's maps a position lies in.

transform_weights(tables, z)

Return the weights that combine a record's moving maps at one position.

Classes

ModalPathTable(z_edges, degree, tables, ...)

Hold a moving guide's snapshots at its sections' Chebyshev-Lobatto points.

solprop.translate.modal_path.GAUGE_PHASE_ATOL = 1e-09#

Largest phase, rad, between two neighbouring samples’ aligned overlap.

The connection is measured in the provider’s own gauge with its stencil aligned to the centre, which removes any phase the gauge accumulates along z; the maps are expanded in the same gauge without that alignment. They agree only where the provider’s gauge has no phase rate of its own, and the two qualified providers’ fields are real up to a fixed factor, so their overlaps are real to rounding. A provider whose gauge drifts would put i phi' on the maps’ diagonal and not on the connection’s, which is refused rather than propagated.

solprop.translate.modal_path.INITIAL_PATH_DEGREE = 4#

Chebyshev degree along z a path’s accuracy search starts at, per section.

Four is five solves per section before anything is measured. The search doubles from here, and the Chebyshev-Lobatto points nest bitwise under a doubling, so no solve is ever repeated (solprop.translate.chebyshev.lobatto_points()).

solprop.translate.modal_path.MAX_PATH_DEGREE = 32#

Highest Chebyshev degree along z a path’s accuracy search tries per section.

The same ceiling solprop.translate.coupled.MAX_SECTION_DEGREE puts on a coupled flow’s sections, and for the same reason: a smooth section of a declared taper resolves at a low degree, and a degree this high means the section spans structure the provider should declare as a kink, or the caller should divide.

class solprop.translate.modal_path.ModalPathTable(z_edges, degree, tables, z_error, gram_departure)[source]#

Bases: Displayed

Hold a moving guide’s snapshots at its sections’ Chebyshev-Lobatto points.

Attributes:
z_edgesnumpy.ndarray

Section edges in m, shape (n_edges_path,), strictly increasing.

degreeint

The Chebyshev degree along z every section is sampled at, at least one; each section holds degree + 1 positions, its two ends included.

tablestuple of solprop.translate.modal_tables.DenseModalTable

One snapshot per position, section by section and increasing inside each, so a position on an edge between two sections appears twice, as the end of one and the start of the next.

z_errorfloat

The measured held-out error of the expansion along z, dimensionless and non-negative: the largest relative maximum-norm departure of E, H or the generator from a fresh solve at the positions a degree doubling adds.

gram_departurefloat

The largest norm(W - I, 2) of the expanded basis at those same positions, on the rule there, dimensionless and non-negative.

Raises:
solprop.exceptions.ModeError

If the counts, the positions or the measurements are inconsistent.

degree: int#
gram_departure: float#
property n_intervals: int[source]#

Return how many sections the path is cut into.

Returns:
int

The section count.

property positions: Float[ndarray, 'n_intervals_positions n_deg_positions'][source]#

Return every sampled position, section by section.

Returns:
numpy.ndarray

Positions in m, shape (n_intervals, degree + 1).

section_tables(index)[source]#

Return one section’s snapshots, increasing in z.

Parameters:
indexint

The section, in [0, n_intervals).

Returns:
tuple of solprop.translate.modal_tables.DenseModalTable

Its degree + 1 snapshots.

tables: tuple[DenseModalTable, ...]#
z_edges: Float[ndarray, 'n_edges_path']#
z_error: float#
solprop.translate.modal_path.PATH_RTOL_FLOOR = 1e-10#

Smallest held-out error along z a path’s accuracy search is asked to reach.

The default target is the frequency interpolation’s own measured error, and a very well sampled band can measure below what a provider’s solves reproduce between two positions: the fields at a node are a sum over a quadrature and an eigenproblem, and their relative round-off is of order 1e-13 to 1e-12. Below this floor the search would be doubling to resolve round-off.

solprop.translate.modal_path.in_frequency(knots, values, omega, inside)[source]#

Return knot values interpolated onto a run’s bins, zero outside the band.

The same not-a-knot cubic in frequency the dense tables interpolate their fields with (solprop.translate.modal_tables.DenseModalTable), so a connection and the maps it is propagated beside are carried onto the run’s grid alike.

Parameters:
knotsnumpy.ndarray

The knots in rad/s, increasing, shape (n_knots,).

valuesnumpy.ndarray

Values at the knots, knot axis first.

omeganumpy.ndarray

The run’s coarse bins in rad/s, shape (n_omega,).

insidenumpy.ndarray

Which bins lie inside the knots, bool, shape (n_omega,).

Returns:
numpy.ndarray

The values with the frequency axis last, zero outside inside.

solprop.translate.modal_path.path_connections(path, provider, *, sections=None, degenerate_blocks=(), basis=None, n_r=None, n_theta=None)[source]#

Measure the moving-basis connection at every sampled position of a path.

solprop.modes.connection.band_connection() at each position and every frequency knot, in the provider’s own gauge with the declared basis applied, which is the gauge the path’s snapshots are in (path_snapshot()). A position on a section’s end is measured one ulp inside it, so that its stencil is taken on that section’s own side of an edge: at a kink of the geometry the slope jumps and the connection with it, and the section that closes there must read its own one-sided limit (the rule of the coupled flow’s solprop.translate.coupled.flow_weights(), applied where it is sampled).

Parameters:
pathModalPathTable

The sampled path.

providersolprop.modes.vector.ModalProvider

The moving guide.

sectionsint or None, optional

How many of the path’s sections to measure, from the first; None measures them all.

degenerate_blockssequence of sequence of int, optional

The declared degenerate blocks.

basissolprop.modes.guide.ChannelBasis or None, optional

The declared constant unitary change of the channel coordinates.

n_r, n_thetaint or None, optional

The resolution each stencil position’s rule is asked for.

Returns:
tuple

Per position of the measured sections, section by section, one ModalConnection per frequency knot.

solprop.translate.modal_path.path_rule(provider, rule, z)[source]#

Return the provider’s own rule at z, asked for exactly as rule was.

The frequency and the resolution are read from rule’s own identity, so the rule at every position of a path is the one the provider would have built there on the same request: its panel edges on that position’s interfaces, its tail certified there. It must keep rule’s structure – the same node count and the same material region at every node – because node q is one moving point along the whole path and the seam carries one region index per node.

Parameters:
providersolprop.modes.vector.ModalProvider

The guide.

rulesolprop.modes.vector.ModalNodeRule

The entrance rule, whose identity names omega, n_r and n_theta.

zfloat

Position in m inside the provider’s z domain.

Returns:
solprop.modes.vector.ModalNodeRule

The rule at z.

Raises:
solprop.exceptions.ModeError

If rule does not say how it was asked for, or the rule at z changes its node count or any node’s material region.

solprop.translate.modal_path.path_sections(provider, sections=1)[source]#

Return the section edges of a moving provider’s path, in m.

The provider’s z domain split at every declared breakpoint strictly inside it – each a kink of the geometry, where the connection jumps with the slope – and each of those intervals divided into sections equal parts.

Parameters:
providersolprop.modes.vector.ModalProvider

The guide, whose z_domain and z_breakpoints are read.

sectionsint, optional

Sections per kink interval, positive; one by default.

Returns:
numpy.ndarray

The edges in m, strictly increasing, from the domain’s start to its end.

Raises:
solprop.exceptions.ModeError

If sections is not a positive integer.

Examples

>>> import numpy as np
>>> from solprop.modes.graded_index import (
...     GradedIndexProvider, PowerLawPermittivity,
... )
>>> from solprop.modes.material import IndexMaterial
>>> from solprop.profiles import PiecewiseLinear
>>> from solprop.translate.modal_path import path_sections
>>> guide = GradedIndexProvider(
...     permittivity=PowerLawPermittivity(IndexMaterial(1.45), 0.005),
...     exterior=IndexMaterial(1.45 * np.sqrt(1 - 2 * 0.005)),
...     radius=PiecewiseLinear(
...         np.array([0.0, 4e-4, 1e-3]), np.array([25e-6, 24e-6, 22.5e-6])
...     ),
...     family="LP", count=2, elements=16, extent=2.0,
... )
>>> [round(one, 6) for one in path_sections(guide, 2).tolist()]
[0.0, 0.0002, 0.0004, 0.0007, 0.001]
solprop.translate.modal_path.path_snapshot(provider, omega, z, nodes, *, degenerate_blocks=(), basis=None, gram_atol=0.0001)[source]#

Solve one position of a path over the band, in the provider’s own gauge.

Unlike solprop.translate.modal_tables.sample_modal_table() the knots are not aligned to one another: they are kept exactly as the provider returns them, with the gauge’s continuity across the band checked instead (GAUGE_PHASE_ATOL), because the connection a moving guide is propagated with is measured in that gauge. A declared basis is applied after, as it is there.

Parameters:
providersolprop.modes.vector.ModalProvider

The guide.

omeganumpy.ndarray

At least four increasing positive frequencies in rad/s, shape (n_omega,).

zfloat

Position in m inside the provider’s z domain.

nodessolprop.modes.vector.ModalNodeRule

The rule at z (path_rule()).

degenerate_blockssequence of sequence of int, optional

The declared degenerate blocks, retained on the table.

basissolprop.modes.guide.ChannelBasis or None, optional

A declared constant unitary change of the channel coordinates.

gram_atolfloat, optional

The table’s construction budget on its sampled Gram defect.

Returns:
solprop.translate.modal_tables.DenseModalTable

A snapshot table at z.

Raises:
solprop.exceptions.ModeError

If the provider’s gauge is not continuous across the band, or on anything the provider or the table refuses.

solprop.translate.modal_path.path_transform_tables(path, omega, *, z_end, profile_error, omega_fine=None, policy_id='dense-knots-cubic-frequency-chebyshev-z')[source]#

Build the flat seam record of a moving guide, its maps expanded along z.

The moving counterpart of solprop.translate.modal_tables.modal_transform_tables(). At every sampled position of every section the path covers up to z_end, the snapshot is evaluated on the run’s coarse grid exactly as a snapshot record’s is – S the stored electric profile, Q = W^-1 conj(e) w / 4 with that position’s area weights – and each section’s samples become Chebyshev coefficients along z (section_coefficients()), which is the (n_intervals, n_deg) layout the record’s maps have always had. The dense reference, its partners, the coordinate dual, the node positions and the weights are stored at the section edges, which are the record’s z_edges; the region index is the path’s one per node.

Outside the band the maps are explicitly zero, as a snapshot’s are.

Parameters:
pathModalPathTable

The sampled path.

omeganumpy.ndarray

Coarse angular frequencies in rad/s, shape (n_omega,), increasing.

z_endfloat

Where the run ends, m; the record keeps every section up to the one that contains it, so it covers the run and nothing is extrapolated.

profile_errorarray_like or float

Declared non-negative profile error per section and coarse frequency, or one value for all.

omega_finearray_like or None, optional

Fine angular frequencies in rad/s; None uses the coarse grid.

policy_idstr, optional

Identity of the numeric policy the record was built under.

Returns:
solprop.translate.modal_tables.ModalTransformTables

The record, with maps of n_intervals sections and degree + 1 coefficients each.

Raises:
solprop.exceptions.ModeError

If the path does not reach z_end, or the record does not validate.

solprop.translate.modal_path.sample_modal_path(provider, omega, rule, z_edges, *, rtol, degree=None, measured=(0.0, 0.0), degenerate_blocks=(), basis=None, gram_atol=0.0001, known=None)[source]#

Sample a moving guide along its sections, at the degree its accuracy demands.

Each section is solved at the Chebyshev-Lobatto points of a degree, starting at INITIAL_PATH_DEGREE, on the provider’s own rule at each position (path_rule()) and in its own gauge (path_snapshot()). The expansion along z is then checked against fresh solves at the points a doubling adds, which are the natural held-out points: they lie between every pair of the current ones and are exactly the solves the next degree needs, so the check costs nothing the doubling would not. The degree is doubled, for every section together because the seam’s degree axis is rectangular, until the largest relative departure of E, H and the generator is within rtol; the degree then kept is the one whose error was measured, not the doubled one, so that the certificate is a measurement of what is stored.

With degree given, nothing is searched or measured: the path is sampled at that degree and carries the measured numbers it was certified with, which is how a run rebuilds the path its guide already qualified.

Parameters:
providersolprop.modes.vector.ModalProvider

The moving guide.

omeganumpy.ndarray

The frequency knots in rad/s, shape (n_omega,), at least four.

rulesolprop.modes.vector.ModalNodeRule

The entrance rule, which every position’s rule is asked for like (path_rule()).

z_edgesnumpy.ndarray

Section edges in m (path_sections()), shape (n_edges,).

rtolfloat

Budget on the held-out departure, positive and finite.

degreeint or None, optional

A degree already qualified, which skips the search.

measuredtuple of float, optional

The held-out error and Gram departure the qualified degree was measured at; carried, not re-measured.

degenerate_blockssequence of sequence of int, optional

The declared degenerate blocks.

basissolprop.modes.guide.ChannelBasis or None, optional

A declared constant unitary change of the channel coordinates.

gram_atolfloat, optional

Each snapshot’s construction budget on its sampled Gram defect.

knownmapping or None, optional

Snapshots already solved, keyed by position, reused rather than solved again.

Returns:
ModalPathTable

The snapshots and the measurements.

Raises:
solprop.exceptions.ModeError

If rtol is not positive and finite, the held-out departure is still above it at MAX_PATH_DEGREE, or on anything a snapshot refuses.

solprop.translate.modal_path.section_coefficients(values)[source]#

Return the Chebyshev coefficients along z of values sampled per section.

Parameters:
valuesnumpy.ndarray

Samples at each section’s Chebyshev-Lobatto points, increasing, shape (n_intervals, degree + 1, *value_shape).

Returns:
numpy.ndarray

The coefficients numpy.polynomial.chebyshev.chebval() evaluates on x = (2 z - lo - hi)/(hi - lo), same shape.

Examples

>>> import numpy as np
>>> from solprop.translate.modal_path import section_coefficients
>>> section_coefficients(np.array([[[1.0 + 0j], [1.0 + 0j]]])).round(12).tolist()
[[[(1+0j)], [0j]]]
solprop.translate.modal_path.transform_section(tables, z)[source]#

Return the section of a record’s maps a position lies in.

The maps are continuous across a section edge – the fields at a node are, even at a kink of the geometry, where only their slope jumps – so which side a position on an edge reads changes the value by the representation error alone and no one-sided rule is needed, unlike the coupled flow’s (solprop.translate.coupled.flow_weights()). A position within 1e-10 of the span of an interior edge takes the section starting there, as solprop.translate.coupled.flow_section() does, and one a rounding outside the span the nearest end’s.

Parameters:
tablessolprop.translate.modal_tables.ModalTransformTables

The record.

zfloat

Position in m.

Returns:
int

The section index, in [0, n_intervals).

solprop.translate.modal_path.transform_weights(tables, z)[source]#

Return the weights that combine a record’s moving maps at one position.

The stage-bundle row of kernel schema 14 (solprop.translate.stage.StageScalars.transform_weights): a backend evaluates S(z) = sum_j w_j S_j and Q(z) = sum_j w_j Q_j over the maps’ (n_intervals, n_deg) coefficient axes flattened, exactly as it evaluates dPhi over its terms. Every weight outside the section containing z (transform_section()) is zero, so no section’s expansion is extrapolated.

None where there is nothing to combine: no record, a record carrying no maps, or maps of one coefficient – one interval of degree zero, a constant geometry – whose one term is the map itself. That keeps the stage bundle of every problem whose maps do not move unchanged.

Parameters:
tablessolprop.translate.modal_tables.ModalTransformTables or None

The record, or None.

zfloat

Position in m.

Returns:
numpy.ndarray or None

Float64, shape (n_intervals * n_deg,), or None.

Examples

>>> from solprop.translate.modal_path import transform_weights
>>> transform_weights(None, 0.5) is None
True