solprop.profiles#

Scalar paths along the propagation axis, with breakpoints and derivatives.

A ZProfile is a scalar function of z: the gas density in a pressure gradient, the core radius of a taper, a pre-ionisation fraction. The cumulative linear operator is built by resolving these paths first and taking the union of the edges they produce as the mandatory split of the expensive array-valued operator (solprop.translate.chebyshev.build_from_paths(), docs/dev/stepper.md section 4), so a profile has to offer three things:

  • vectorised evaluation, because the builder samples at Chebyshev points in blocks;

  • declared breakpoints, because a stage boundary is a hard kink that the adaptive split must never straddle and the stepper must land on;

  • an analytic derivative where one exists, and an honest declaration where it does not, so that a caller can tell a converged quantity from a differenced one.

A profile is directly usable as a path: build_from_paths([profile], ...) works through __call__, and the caller passes the union of the profiles’ breakpoints as mandatory_breakpoints.

Pressures are in bar at this interface, the one documented departure from SI (docs/dev/conventions.md section 1); positions are in metres and densities in m^-3.

Vacuum ends. The steady-flow fill \(p(z) = \sqrt{p_0^2 + (z/L)(p_1^2 - p_0^2)}\) has \(\mathrm{d}p/\mathrm{d}z = (p_1^2 - p_0^2)/(2Lp)\), which diverges where \(p \to 0\). A fill that reaches exactly zero therefore caps every integration scheme at about third order however small the step (docs/dev/stepper.md section 10, trap 5), silently. PressureGradient refuses such a stage end and substitutes a small floor, logging what it did; the floor is relative to the highest pressure of the fill, so it behaves the same for a 0.8 bar gradient and a 40 bar one. A fill built by solprop.gasflow.steady_fill() from the gas and the bore replaces a vacuum end by its choked pressure instead, which is physical rather than numerical.

Tapers. Given the core radius, PressureGradient makes \(p^2\) linear in the hydraulic resistance \(\int a^{-4}\,\mathrm{d}z\) rather than in z (Pennetta et al., APL Photonics 4, 056105 (2019), eq. 6), so a narrow waist carries most of the pressure drop; hydraulic_integral() evaluates that resistance.

Module Attributes

DEFAULT_PRESSURE_FLOOR_FRACTION

Default vacuum-end pressure floor, as a fraction of the highest stage pressure.

MIN_STAGE_PRESSURE

Smallest highest-stage pressure a fill accepts, bar.

Functions

as_profile(value[, span])

Coerce a number to a constant path, and pass a path through unchanged.

hydraulic_integral(radius, positions, *[, ...])

Return the hydraulic resistance int w a^-4 dz of each stage of a bore.

profile_identity(profile)

Return the structural identity of a path, exact to the last bit.

Classes

Constant(value[, span])

A path that does not vary along z.

DensityPath(gas, pressure[, temperature])

The number density along z of a gas following a pressure path.

FromCallable(func, span[, kinks, slope, ...])

An arbitrary vectorised path, with its breakpoints declared by the caller.

Linear(start, end, length)

A path varying linearly between two ends and constant outside them.

PathVariation(knots, first, last, variation, ...)

The monotone structure of a path over a closed interval.

PiecewiseLinear(positions, values)

A path linear between given positions and constant outside them.

PressureGradient(positions, pressures, floor)

A steady-flow pressure fill, in bar, with every stage boundary a breakpoint.

Scaled(path, factor)

Multiply a path and its derivative by a constant.

SeriesOfPath(path, series)

A smooth function of another path, f(g(z)), with f a Chebyshev series.

Sum(paths)

Add compatible paths in their declared order.

ZProfile(*args, **kwargs)

A scalar path along the propagation axis.

class solprop.profiles.Constant(value, span=(0.0, inf))[source]#

Bases: object

A path that does not vary along z.

Attributes:
valuefloat

The constant value, in whatever unit the consumer expects.

spantuple of float

The interval in m the path is declared over. The default covers any propagation, since a constant needs no domain.

Examples

>>> import numpy as np
>>> from solprop.profiles import Constant
>>> radius = Constant(125e-6)
>>> radius(np.array([0.0, 1.0]))
array([0.000125, 0.000125])
>>> radius.derivative(np.array([0.5]))
array([0.])
property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the interval edges, which for a constant are none.

Returns:
numpy.ndarray

An empty array: a constant has no kinks.

derivative(z)[source]#

Return zero at each position.

Parameters:
znumpy.ndarray

Positions in m, shape (n_z,).

Returns:
numpy.ndarray

Zeros of shape (n_z,).

property derivative_is_analytic: bool[source]#

Return whether the derivative is exact.

Returns:
bool

Always True: the derivative is identically zero.

property is_constant: bool[source]#

Return whether the path is the same everywhere.

Returns:
bool

Always True.

property jumps: Float[ndarray, 'n_jumps'][source]#

Return the declared discontinuities, which for a constant are none.

Returns:
numpy.ndarray

Positions in m.

span: tuple[float, float] = (0.0, inf)#
value: float#
variation(start, end)[source]#

Return the exact variation, which is zero.

Parameters:
start, endfloat

The closed interval in m, finite with start < end.

Returns:
PathVariation

One flat piece.

property z_span: tuple[float, float][source]#

Return the interval the path is defined over.

Returns:
tuple of float

(start, end) in m.

solprop.profiles.DEFAULT_PRESSURE_FLOOR_FRACTION: Final = 0.0001#

Default vacuum-end pressure floor, as a fraction of the highest stage pressure.

At 1e-4 of the peak the residual gas contributes a relative refractivity of 1e-4 of the fill – nothing, for a gradient whose point is that the end is empty – while the slope of the steady-flow profile stays finite and cheap for the adaptive Chebyshev split to resolve. Being relative rather than absolute, it means the same thing for a 0.8 bar gradient as for a 40 bar one.

class solprop.profiles.DensityPath(gas, pressure, temperature=293.15)[source]#

Bases: Displayed

The number density along z of a gas following a pressure path.

The equation of state is a scalar call into optmat (CoolProp) that costs about a millisecond, and the Chebyshev builder samples a path thousands of times, so the density is tabulated once over the pressure range the path spans and interpolated with a cubic spline. The spline’s derivative is exact, so d(rho)/dz = rho'(p) dp/dz stays analytic whenever the pressure path is.

A temperature that varies along z makes the density rho(p(z), T(z)). The compressibility factor Z(p, T) = p / (rho k_B T) is then tabulated as a tensor Chebyshev series over the rectangle of pressures and temperatures the two paths span, converged to 1e-13 on each axis (a few hundred equation-of-state calls, fewer than the constant-temperature spline’s 1024), and rho = p / (k_B T Z) keeps its ideal-gas part exact. The derivative is rho_p p' + rho_T T' with the series’ exact partial derivatives. A constant temperature, a number or a constant path, takes the spline above, bit for bit.

Attributes:
gasGas or str or Species

The species: a built-in gas’s name takes optmat’s equation of state, a species its own.

pressureZProfile

The pressure path in bar.

temperaturefloat or ZProfile

Temperature in K, > 0: a number, or a path along z. A constant path is stored as its value.

Examples

>>> import numpy as np
>>> from solprop.physdata import Gas
>>> from solprop.profiles import DensityPath, Linear, PressureGradient
>>> fill = PressureGradient.two_point(1.0, 1.0, 0.5)
>>> path = DensityPath(Gas.He, fill)
>>> f"{float(path(np.array([0.0]))[0]):.6e}"
'2.469537e+25'
>>> bool(path.derivative(np.array([0.5]))[0] < 0.0)
True
>>> warm = DensityPath(Gas.He, fill, Linear(293.15, 350.0, 1.0))
>>> f"{float(warm(np.array([0.0]))[0]):.6e}"
'2.469537e+25'
property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the breakpoints of the pressure path, and of a temperature path.

Returns:
numpy.ndarray

The positions in m; the density kinks exactly where the pressure or the temperature does.

derivative(z)[source]#

Return the density gradient at each position.

Parameters:
znumpy.ndarray

Positions in m, shape (n_z,).

Returns:
numpy.ndarray

d(rho)/dz in m^-3 per metre, by the chain rule through the pressure path, and through a temperature path rho_p p' + rho_T T'.

property derivative_is_analytic: bool[source]#

Return whether the derivative is exact.

Returns:
bool

True when the pressure path’s derivative is, and a temperature path’s: the tables contribute exact derivatives of their interpolants.

gas: Gas | str | Species#
property is_constant: bool[source]#

Return whether the density never varies.

Returns:
bool

True when the pressure never varies and neither does the temperature, or the pressure is identically zero.

property jumps: Float[ndarray, 'n_jumps'][source]#

Return the declared discontinuities of the pressure and temperature paths.

Returns:
numpy.ndarray

Positions in m; the density jumps only where an argument does.

pressure: ZProfile#
temperature: float | ZProfile = 293.15#
variation(start, end)[source]#

Enclose the variation through the pressure path’s monotone pieces.

The density is the tabulated cubic spline of the pressure. On a monotone pressure piece the density is monotone between the spline’s critical points in the piece’s pressure range, which are the real roots of its piecewise quadratic derivative, found in closed form; the variation is then exact from the values there. A pressure piece not certified monotone is bounded by the spline’s largest slope on its range times the pressure variation.

Parameters:
start, endfloat

The closed interval in m, finite with start < end.

Returns:
PathVariation

The record of the tabulated density, constant outside the span.

Raises:
TranslationError

If the temperature varies along z: that two-dimensional table has no enclosure here, and the spatial routes do not carry one.

property z_span: tuple[float, float][source]#

Return the interval the path covers.

Returns:
tuple of float

(start, end) in m, from the pressure path; from a temperature path where the pressure’s is unbounded.

class solprop.profiles.FromCallable(func, span, kinks=<factory>, slope=None, jumps=<factory>, turning=None)[source]#

Bases: object

An arbitrary vectorised path, with its breakpoints declared by the caller.

The escape hatch for a measured or exotic profile. Because the adaptive Chebyshev split finds smooth structure but cannot see a kink it was not told about, the breakpoints are a required argument rather than something guessed.

With no slope the derivative is a Richardson-extrapolated central difference, derivative_is_analytic is False, and a caller that needs an analytic beta_1 (ADR-006) must supply one.

An arbitrary callable has no knowable variation: sampling it, or its derivative, at finitely many points proves nothing between them. A caller that needs one (the adiabatic certificate of solprop.adiabatic) declares it: turning lists every position where the path may change direction, so that it is monotone between consecutive members of the span’s ends, turning, kinks and jumps. Without that declaration variation() refuses.

Attributes:
funccollections.abc.Callable

The path, vectorised over an array of positions in m.

spantuple of float

The interval in m the path is defined over, as (start, end).

kinksnumpy.ndarray

Positions in m where the path kinks, strictly increasing. May be empty.

slopecollections.abc.Callable or None

The analytic derivative, if there is one.

jumpsnumpy.ndarray

Positions in m strictly inside the span where the path is discontinuous, strictly increasing. The callable’s value at a jump is its limit from the right, and the limit from the left is its value at the next float below. May be empty; every jump is also a breakpoint.

turningnumpy.ndarray or None

Declared positions in m, inside the span and strictly increasing, where the path may turn; None (the default) declares nothing and leaves the variation unknown.

Examples

>>> import numpy as np
>>> from solprop.profiles import FromCallable
>>> path = FromCallable(np.sin, (0.0, 1.0), slope=np.cos)
>>> path.derivative_is_analytic
True
>>> bool(abs(path.derivative(np.array([0.3]))[0] - np.cos(0.3)) < 1e-15)
True
property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the declared kinks and jumps.

Returns:
numpy.ndarray

The positions in m, sorted and unique.

derivative(z)[source]#

Return the derivative at each position.

Parameters:
znumpy.ndarray

Positions in m, shape (n_z,).

Returns:
numpy.ndarray

The analytic derivative when one was supplied, otherwise a Richardson-extrapolated central difference, whose relative accuracy is about 1e-10 for a smooth path and no better.

property derivative_is_analytic: bool[source]#

Return whether the derivative is exact.

Returns:
bool

True only when a slope was supplied.

func: Callable[[Float[ndarray, 'n_z']], Float[ndarray, 'n_z']]#
property is_constant: bool[source]#

Return whether the path is known to be constant.

Returns:
bool

Always False: an arbitrary callable cannot be inspected, and the conservative answer only costs a re-evaluation.

jumps: Float[ndarray, 'n_jumps']#
kinks: Float[ndarray, 'n_kinks']#
slope: Callable[[Float[ndarray, 'n_z']], Float[ndarray, 'n_z']] | None = None#
span: tuple[float, float]#
turning: Float[ndarray, 'n_turning'] | None = None#
variation(start, end)[source]#

Return the variation the declaration fixes.

Parameters:
start, endfloat

The closed interval in m, inside the span, finite with start < end.

Returns:
PathVariation

Monotone pieces between the declared positions, from the callable’s values and its one-sided limits at the jumps.

Raises:
TranslationError

If no turning points were declared, or the interval leaves the span, where the callable’s continuation is unknown.

property z_span: tuple[float, float][source]#

Return the declared span.

Returns:
tuple of float

(start, end) in m.

class solprop.profiles.Linear(start, end, length)[source]#

Bases: Displayed

A path varying linearly between two ends and constant outside them.

A two-stage PiecewiseLinear, named for the common case of a taper.

Attributes:
startfloat

Value at z = 0.

endfloat

Value at z = length.

lengthfloat

Length of the varying section in m, > 0.

Examples

>>> import numpy as np
>>> from solprop.profiles import Linear
>>> taper = Linear(125e-6, 75e-6, 1.5)
>>> taper(np.array([0.0, 0.75, 1.5])) * 1e6
array([125., 100.,  75.])
property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the two ends of the varying section.

Returns:
numpy.ndarray

[0, length] in m.

derivative(z)[source]#

Return the slope at each position.

Parameters:
znumpy.ndarray

Positions in m, shape (n_z,).

Returns:
numpy.ndarray

(end - start) / length inside the section and zero outside.

property derivative_is_analytic: bool[source]#

Return whether the derivative is exact.

Returns:
bool

Always True.

end: float#
property is_constant: bool[source]#

Return whether the two ends carry the same value.

Returns:
bool

True when start == end.

property jumps: Float[ndarray, 'n_jumps'][source]#

Return the declared discontinuities, which for a linear path are none.

Returns:
numpy.ndarray

Positions in m.

length: float#
start: float#
variation(start, end)[source]#

Return the exact variation of the two-stage table.

Parameters:
start, endfloat

The closed interval in m, finite with start < end.

Returns:
PathVariation

The record of the equivalent PiecewiseLinear.

property z_span: tuple[float, float][source]#

Return the interval the path varies over.

Returns:
tuple of float

(0, length) in m.

solprop.profiles.MIN_STAGE_PRESSURE: Final = 1e-150#

Smallest highest-stage pressure a fill accepts, bar.

The steady-flow profile is linear in p^2 and its slope divides by p, so every pressure the fill evaluates – the default floor of DEFAULT_PRESSURE_FLOOR_FRACTION times the highest stage included – must square to a normal float. (1e-4 * 1e-150)^2 = 1e-308 is the smallest normal double; below this the floored end squares to zero and the slope there is infinite. Found by hypothesis: a fill from 0 to 6.5e-159 bar passed the old exact-zero guard and returned an infinite derivative at the entrance.

class solprop.profiles.PathVariation(knots, first, last, variation, low, high, monotone)[source]#

Bases: object

The monotone structure of a path over a closed interval.

The interval is cut at knots into pieces. On each open piece the path is continuous; its one-sided limits at the piece’s ends are first and last, its values lie in [low, high] and its total variation inside the piece is at most variation. Where monotone holds the path is monotone on the piece and variation == abs(last - first) exactly. A difference between one piece’s last and the next piece’s first is a declared jump.

The record describes the represented path, the function the class evaluates, not a physical profile it may approximate; an interpolation error is a separate envelope.

Attributes:
knotsnumpy.ndarray

Piece edges in m, strictly increasing, at least two.

first, lastnumpy.ndarray

Limits of the path at each piece’s start from the right and at its end from the left, shape (n_pieces,).

variationnumpy.ndarray

Upper bound of the total variation inside each open piece, at least abs(last - first).

low, highnumpy.ndarray

Lower and upper bounds of the path on each closed piece, limits included.

monotonenumpy.ndarray

Whether each piece is certified monotone, so that its variation is exact.

Raises:
TranslationError

If the arrays are inconsistent or the bounds do not enclose the limits.

Examples

>>> from solprop.profiles import PiecewiseLinear
>>> path = PiecewiseLinear.from_stages([0.0, 1.0, 2.0], [0.0, 2.0, 1.0])
>>> record = path.variation(0.0, 2.0)
>>> float(record.total_variation)
3.0
>>> record.jumps.size
0
collapse()[source]#

Return the same enclosure as one piece over the whole interval.

Returns:
PathVariation

One piece whose variation includes every interior jump; it is monotone only when every piece is, no jump reverses, and all move one way.

first: Float[ndarray, 'n_pieces']#
high: Float[ndarray, 'n_pieces']#
property jumps: Float[ndarray, 'n_jumps'][source]#

Return the interior knots where the path jumps.

Returns:
numpy.ndarray

Positions in m where the next piece’s first differs from last.

knots: Float[ndarray, 'n_knots']#
last: Float[ndarray, 'n_pieces']#
low: Float[ndarray, 'n_pieces']#
monotone: Bool[ndarray, 'n_pieces']#
scaled(factor)[source]#

Return the enclosure of the path multiplied by a constant.

Parameters:
factorfloat

Finite multiplier.

Returns:
PathVariation

The scaled record; a negative factor swaps the bounds.

property total_variation: float[source]#

Return the enclosed total variation over the whole interval.

Returns:
float

The sum of the pieces’ variations and the jumps’ magnitudes, in the unit of the path.

variation: Float[ndarray, 'n_pieces']#
class solprop.profiles.PiecewiseLinear(positions, values)[source]#

Bases: Displayed

A path linear between given positions and constant outside them.

Every position is a breakpoint, because the slope jumps there. Clamping outside the declared range rather than extrapolating keeps the path bounded when the Chebyshev table is built slightly beyond the fibre end.

Attributes:
positionsnumpy.ndarray

Stage boundaries in m, strictly increasing, at least two.

valuesnumpy.ndarray

The value at each position.

Examples

>>> import numpy as np
>>> from solprop.profiles import PiecewiseLinear
>>> taper = PiecewiseLinear.from_stages([0.0, 1.0], [125e-6, 75e-6])
>>> taper(np.array([0.0, 0.5, 1.0, 2.0])) * 1e6
array([125., 100.,  75.,  75.])
>>> taper.derivative(np.array([0.5])) * 1e6
array([-50.])
property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the stage boundaries, every one of which is a kink.

Returns:
numpy.ndarray

The positions, in m.

derivative(z)[source]#

Return the slope at each position.

The value at a breakpoint is the slope of the stage that starts there, and zero outside the declared range; the one-sided choice is arbitrary at a kink and the Chebyshev builder never samples one, because a breakpoint is an interval edge.

Parameters:
znumpy.ndarray

Positions in m, shape (n_z,).

Returns:
numpy.ndarray

d(value)/dz per metre, shape (n_z,).

property derivative_is_analytic: bool[source]#

Return whether the derivative is exact.

Returns:
bool

Always True.

classmethod from_stages(positions, values)[source]#

Build a path from any sequence of positions and values.

Parameters:
positionsarray_like

Stage boundaries in m, strictly increasing, at least two.

valuesarray_like

The value at each position.

Returns:
PiecewiseLinear

The path.

Examples

>>> from solprop.profiles import PiecewiseLinear
>>> PiecewiseLinear.from_stages([0.0, 1.0], [1.0, 3.0]).values
array([1., 3.])
property is_constant: bool[source]#

Return whether every stage carries the same value.

Returns:
bool

True when the path never varies.

property jumps: Float[ndarray, 'n_jumps'][source]#

Return the declared discontinuities, none for a continuous interpolant.

Returns:
numpy.ndarray

Positions in m.

positions: Float[ndarray, 'n_positions']#
values: Float[ndarray, 'n_values']#
variation(start, end)[source]#

Return the exact variation: the path is monotone between its stages.

Parameters:
start, endfloat

The closed interval in m, finite with start < end.

Returns:
PathVariation

One monotone piece per stage inside the interval, and flat pieces outside the declared range.

property z_span: tuple[float, float][source]#

Return the interval the stages cover.

Returns:
tuple of float

(first position, last position) in m.

class solprop.profiles.PressureGradient(positions, pressures, floor, radius=None, choked_ends=(False, False), weight=None)[source]#

Bases: Displayed

A steady-flow pressure fill, in bar, with every stage boundary a breakpoint.

Laminar flow of a compressible gas through a narrow tube of constant bore makes \(p^2\) linear in z, so between two stage ends

\[p(z) = \sqrt{p_i^2 + \frac{z - z_i}{z_{i+1} - z_i}(p_{i+1}^2 - p_i^2)}\]

evaluated as the convex combination \(\sqrt{(1-f)\,p_i^2 + f\,p_{i+1}^2}\), which cannot cancel, and the pressure is held constant outside the declared range. The derivative \(\mathrm{d}p/\mathrm{d}z = (p_{i+1}^2 - p_i^2)/(2\,\Delta z\,p)\) is exact.

Given a varying core radius \(a(z)\), the mass flow of an isothermal ideal gas in Poiseuille flow, \(\dot m \propto a^4\,p\,\mathrm{d}p/\mathrm{d}z\), is constant along each stage, so \(p^2\) is linear in the hydraulic resistance instead (Pennetta et al., APL Photonics 4, 056105 (2019), eq. 6):

\[p^2 = (1 - f)\,p_i^2 + f\,p_{i+1}^2, \quad f(z) = \frac{R(z_i, z)}{R(z_i, z_{i+1})}, \quad R(u, v) = \int_u^v a^{-4}\,\mathrm{d}z,\]

with \(\mathrm{d}p/\mathrm{d}z = (p_{i+1}^2 - p_i^2)\,a^{-4}/(2 R p)\), still analytic. Every stage is an independent flow between two gas cells. A constant or omitted radius is the uniform law above, bit for bit.

A gas whose temperature varies along the fibre carries the mass flow \(\dot m \propto (p/T)\,a^4\,\mathrm{d}p/\mathrm{d}z/\eta(T)\), so the integrand of \(R\) becomes \(w\,a^{-4}\) with the flow weight \(w = \eta(T)\,T\), which solprop.gasflow.steady_fill() builds from the gas’s viscosity; the fill itself needs only its variation, since a constant factor cancels from \(f\). A constant or omitted weight is the law above, bit for bit.

A stage pressure of zero makes that derivative infinite and caps every integration scheme at about third order (docs/dev/stepper.md section 10, trap 5). Such a pressure is raised to floor and the substitution is logged. Passing floor=0.0 keeps the true vacuum end, for reproducing a result that assumes one, and logs a stronger warning. A physical vacuum end is choked rather than floored: build the fill with solprop.gasflow.steady_fill(), which knows the gas.

Attributes:
positionsnumpy.ndarray

Stage boundaries in m, strictly increasing, at least two.

pressuresnumpy.ndarray

Absolute pressure in bar at each boundary, >= 0, after any flooring.

floorfloat

The pressure floor in bar that was applied.

radiusZProfile or None

The core radius in m the flow follows, or None for a uniform bore. A constant radius is stored as None: it is the uniform law exactly.

choked_endstuple of bool

Whether the first and the last end pressure is a choked pressure set by solprop.gasflow.steady_fill(), which depends on the gas; (False, False) for a fill whose ends are the declared ones.

weightZProfile or None

The flow weight multiplying a^-4 in the resistance, positive, in any unit (eta(T) T in Pa s K for a temperature path), or None for none. A constant weight is stored as None: it cancels exactly.

Examples

>>> import numpy as np
>>> from solprop.profiles import PressureGradient
>>> fill = PressureGradient.two_point(1.5, 0.8, 0.0)
>>> float(fill.floor)
8e-05
>>> [f"{value:.4f}" for value in fill(np.array([0.0, 0.75, 1.5]))]
['0.8000', '0.5657', '0.0001']
>>> fill.breakpoints
array([0. , 1.5])
property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the stage boundaries and the bore’s kinks, every one a hard kink.

Returns:
numpy.ndarray

The positions in m: the stage boundaries, joined for a fill that follows a varying bore or carries a flow weight by their breakpoints inside the fill, where the slope of the pressure kinks with theirs.

choked_ends: tuple[bool, ...] = (False, False)#
derivative(z)[source]#

Return the pressure gradient in bar per metre at each position.

Parameters:
znumpy.ndarray

Positions in m, shape (n_z,).

Returns:
numpy.ndarray

dp/dz = (p_next^2 - p^2) / (2 dz p) on a uniform bore and (p_next^2 - p^2) w a^-4 / (2 R p) on a varying one or with a flow weight w, and zero outside the fill. It is infinite at a stage end held at exactly zero pressure, which is why the constructor floors one by default.

property derivative_is_analytic: bool[source]#

Return whether the derivative is exact.

Returns:
bool

Always True.

floor: float#
classmethod from_stages(positions, pressures, *, floor=None, radius=None, weight=None)[source]#

Build a multi-stage fill, applying and logging the vacuum-end floor.

Parameters:
positionsarray_like

Stage boundaries in m, strictly increasing, at least two.

pressuresarray_like

Absolute pressure in bar at each boundary, >= 0.

floorfloat or None, optional

Smallest pressure in bar any stage end may take. None uses DEFAULT_PRESSURE_FLOOR_FRACTION of the highest stage pressure; 0.0 keeps a true vacuum end and logs that the order of the scheme is then capped.

radiusfloat, ZProfile or None, optional

Core radius in m, > 0, which the flow follows (the class docstring); None or a constant is the uniform bore. A radius that is neither constant nor linear between its breakpoints is integrated by Chebyshev interpolation and refused if that does not converge.

weightfloat, ZProfile or None, optional

Flow weight multiplying a^-4, > 0 (the class docstring), integrated with the bore by Chebyshev interpolation; None or a constant is none.

Returns:
PressureGradient

The fill, with any pressure below floor raised to it.

Raises:
TranslationError

If the stage table is invalid, floor is negative, every stage pressure is zero, the highest is too small for its square to be representable, or the radius is not positive or cannot be integrated.

Examples

>>> from solprop.profiles import PressureGradient
>>> fill = PressureGradient.from_stages([0.0, 0.6, 1.0], [0.0, 5.0, 0.0])
>>> fill.pressures
array([5.e-04, 5.e+00, 5.e-04])
property is_constant: bool[source]#

Return whether every stage carries the same pressure.

Returns:
bool

True when the fill never varies.

property jumps: Float[ndarray, 'n_jumps'][source]#

Return the declared discontinuities, which for a steady-flow fill are none.

Returns:
numpy.ndarray

Positions in m.

positions: Float[ndarray, 'n_positions']#
pressures: Float[ndarray, 'n_pressures']#
radius: ZProfile | None = None#
classmethod two_point(length, start_bar, end_bar, *, floor=None, radius=None, weight=None)[source]#

Build a single-stage fill from one end pressure to the other.

Parameters:
lengthfloat

Length of the fill in m, > 0.

start_bar, end_barfloat

Absolute pressures in bar at z = 0 and z = length, >= 0.

floorfloat or None, optional

Smallest pressure in bar any stage end may take. None uses DEFAULT_PRESSURE_FLOOR_FRACTION of the highest stage pressure.

radiusfloat, ZProfile or None, optional

Core radius in m, > 0, which the flow follows; None or a constant is the uniform bore.

weightfloat, ZProfile or None, optional

Flow weight multiplying a^-4, > 0 (the class docstring); None or a constant is none.

Returns:
PressureGradient

The fill.

Raises:
TranslationError

If the length is not positive, or a pressure is negative.

Examples

>>> from solprop.profiles import PressureGradient
>>> PressureGradient.two_point(1.0, 2.0, 2.0).pressures
array([2., 2.])
variation(start, end)[source]#

Return the exact variation: the fill is monotone on every stage.

On a stage p^2 is the convex combination of its end values with the weight f, and f is the running hydraulic resistance over its total: the integral of a positive weight, so increasing. The pressure therefore runs monotonically from one stage pressure to the next, and the bore’s own kinks never turn it.

Parameters:
start, endfloat

The closed interval in m, finite with start < end.

Returns:
PathVariation

One monotone piece per stage inside the interval, and flat pieces outside the fill.

weight: ZProfile | None = None#
property z_span: tuple[float, float][source]#

Return the interval the fill covers.

Returns:
tuple of float

(first position, last position) in m.

class solprop.profiles.Scaled(path, factor)[source]#

Bases: object

Multiply a path and its derivative by a constant.

Attributes:
pathZProfile

Underlying path, with its units and endpoint continuation.

factorfloat

Finite multiplier; the output units include those of this multiplier.

property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the underlying breakpoints in m.

Returns:
numpy.ndarray

Positions of derivative discontinuities.

derivative(z)[source]#

Evaluate the scaled derivative.

Parameters:
znumpy.ndarray

Positions in m.

Returns:
numpy.ndarray

Scaled derivatives per metre, with the input shape.

property derivative_is_analytic: bool[source]#

Return whether the underlying derivative is analytic.

Returns:
bool

The underlying path’s declaration.

factor: float#
property is_constant: bool[source]#

Return whether the scaled path is constant.

Returns:
bool

True for a constant path or a zero multiplier.

property jumps: Float[ndarray, 'n_jumps'][source]#

Return the underlying declared discontinuities in m.

Returns:
numpy.ndarray

Positions where the path jumps.

path: ZProfile#
variation(start, end)[source]#

Return the underlying record scaled by the factor.

Parameters:
start, endfloat

The closed interval in m, finite with start < end.

Returns:
PathVariation

The scaled record, exact where the underlying one is.

property z_span: tuple[float, float][source]#

Return the underlying span in m.

Returns:
tuple of float

Start and end positions.

class solprop.profiles.SeriesOfPath(path, series)[source]#

Bases: Displayed

A smooth function of another path, f(g(z)), with f a Chebyshev series.

The steady-flow fill of a gas whose temperature varies along the fibre is weighted by eta(T) T (PressureGradient): solprop.gasflow.steady_fill() represents that weight as a converged Chebyshev series in the temperature, evaluated on the temperature path. The derivative f'(g) g' is analytic whenever the inner path’s is.

Attributes:
pathZProfile

The inner path g, in whatever unit series takes.

seriesnumpy.polynomial.Chebyshev

The outer function f, with its domain in the unit of path; outside the domain it extrapolates, so it should cover the path’s range.

Examples

>>> import numpy as np
>>> from numpy.polynomial import Chebyshev
>>> from solprop.profiles import Linear, SeriesOfPath
>>> square = Chebyshev.fit([1.0, 2.0, 3.0], [1.0, 4.0, 9.0], 2, domain=[1.0, 3.0])
>>> weight = SeriesOfPath(Linear(1.0, 3.0, 2.0), square)
>>> [round(float(v), 12) for v in weight(np.array([0.0, 1.0]))]
[1.0, 4.0]
>>> round(float(weight.derivative(np.array([1.0]))[0]), 12)
4.0
property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the inner path’s breakpoints.

Returns:
numpy.ndarray

The positions in m; a smooth function kinks where its argument does.

derivative(z)[source]#

Return f'(g(z)) g'(z) at each position.

Parameters:
znumpy.ndarray

Positions in m, shape (n_z,).

Returns:
numpy.ndarray

The slope per metre, shape (n_z,).

property derivative_is_analytic: bool[source]#

Return whether the derivative is exact.

Returns:
bool

True when the inner path’s is: the series has an exact derivative.

property is_constant: bool[source]#

Return whether the inner path is constant.

Returns:
bool

True when the argument never varies.

property jumps: Float[ndarray, 'n_jumps'][source]#

Return the inner path’s declared discontinuities.

Returns:
numpy.ndarray

Positions in m.

path: ZProfile#
series: Chebyshev#
variation(start, end)[source]#

Enclose the variation through the series’ critical points.

Parameters:
start, endfloat

The closed interval in m, finite with start < end.

Returns:
PathVariation

The composite record: exact on a monotone inner piece between the series’ near-real critical points, otherwise the series’ derivative bound times the inner variation.

Raises:
TranslationError

If the inner path leaves the series’ domain, where it extrapolates.

property z_span: tuple[float, float][source]#

Return the inner path’s interval.

Returns:
tuple of float

(start, end) in m.

class solprop.profiles.Sum(paths)[source]#

Bases: object

Add compatible paths in their declared order.

Attributes:
pathstuple of ZProfile

Non-empty sequence of paths with compatible units. Their spans are joined; outside an individual span that path supplies its endpoint continuation.

property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the union of the component breakpoints in m.

Returns:
numpy.ndarray

Sorted unique positions.

derivative(z)[source]#

Evaluate the derivative sum in component order.

Parameters:
znumpy.ndarray

Positions in m.

Returns:
numpy.ndarray

Summed derivatives per metre with the input shape.

property derivative_is_analytic: bool[source]#

Return whether every component derivative is analytic.

Returns:
bool

Conjunction of the component declarations.

property is_constant: bool[source]#

Return whether every component is constant.

Returns:
bool

Conjunction of the component declarations.

property jumps: Float[ndarray, 'n_jumps'][source]#

Return the union of the component discontinuities in m.

Returns:
numpy.ndarray

Sorted unique positions.

paths: tuple[ZProfile, ...]#
variation(start, end)[source]#

Enclose the variation of the sum on the union of the component pieces.

On a piece where every component is certified monotone in the same direction the sum is monotone and its variation exact; otherwise the variation is at most the sum of the components’, the triangle inequality.

Parameters:
start, endfloat

The closed interval in m, finite with start < end.

Returns:
PathVariation

The record on the union of the components’ knots.

property z_span: tuple[float, float][source]#

Return the union of the component spans in m.

Returns:
tuple of float

Earliest start and latest end.

class solprop.profiles.ZProfile(*args, **kwargs)[source]#

Bases: Protocol

A scalar path along the propagation axis.

Implementations are frozen dataclasses. Every one is callable on an array of positions and returns an array of the same shape, so that it can be handed straight to solprop.translate.chebyshev.build_from_paths() as a path.

property breakpoints: Float[ndarray, 'n_breakpoints'][source]#

Return the positions in m where the path jumps or kinks, increasing.

derivative(z)[source]#

Return the derivative of the path with respect to z at each position.

Parameters:
znumpy.ndarray

Positions along the waveguide in m, shape (n_z,).

Returns:
numpy.ndarray

d(value)/dz per metre at each position, shape (n_z,).

property derivative_is_analytic: bool[source]#

Whether derivative() is exact rather than a finite difference.

property is_constant: bool[source]#

Whether the path takes the same value everywhere on its span.

property jumps: Float[ndarray, 'n_jumps'][source]#

Return the declared discontinuities in m, a subset of the breakpoints.

variation(start, end)[source]#

Enclose the monotone structure and total variation of the path.

Parameters:
start, endfloat

The closed interval in m, finite with start < end.

Returns:
PathVariation

Pieces on which the path’s values and variation are enclosed.

property z_span: tuple[float, float][source]#

The interval in m over which the path is defined, as (start, end).

solprop.profiles.as_profile(value, span=(0.0, inf))[source]#

Coerce a number to a constant path, and pass a path through unchanged.

Parameters:
valuefloat or ZProfile

A constant value, or a path already.

spantuple of float, optional

The interval in m given to a Constant built from a number.

Returns:
ZProfile

The path.

Raises:
TranslationError

If value is neither a real number nor a path.

Examples

>>> import numpy as np
>>> from solprop.profiles import as_profile
>>> as_profile(125e-6)(np.array([0.0, 2.0])) * 1e6
array([125., 125.])
solprop.profiles.hydraulic_integral(radius, positions, *, weight=None)[source]#

Return the hydraulic resistance int w a^-4 dz of each stage of a bore.

The Poiseuille throughput of a stage is pi (p_high^2 - p_low^2) / (16 eta R) with R this integral without a weight: length / a^4 for a uniform bore. A gas whose temperature varies along the fibre takes the weight w = eta(T) T (PressureGradient).

Parameters:
radiusfloat or ZProfile

Core radius in m, > 0 over the stages.

positionsarray_like

Stage boundaries in m, strictly increasing, at least two.

weightfloat, ZProfile or None, optional

A flow weight multiplying a^-4, > 0 over the stages, in any unit. None (the default) is no weight.

Returns:
numpy.ndarray

int_{z_i}^{z_{i+1}} w a^-4 dz in m^-3 times the unit of weight, one per stage.

Raises:
TranslationError

If the positions are invalid, or the radius or the weight is not positive or cannot be integrated.

Examples

>>> from solprop.profiles import Linear, hydraulic_integral
>>> f"{hydraulic_integral(125e-6, [0.0, 1.0])[0]:.6e}"
'4.096000e+15'
>>> taper = hydraulic_integral(Linear(125e-6, 75e-6, 1.0), [0.0, 1.0])
>>> f"{taper[0]:.6e}"
'1.238914e+16'
solprop.profiles.profile_identity(profile)[source]#

Return the structural identity of a path, exact to the last bit.

Two paths give the same identity exactly when they are the same class built from the same numbers. Unlike repr, the result does not depend on numpy.set_printoptions and does not summarise a long array, so a 1200-knot PiecewiseLinear is distinguished from any other. Every value is a string, an integer or a nested mapping of them, so the result is JSON-able and can go straight into a provider’s identity.

Parameters:
profilefloat or ZProfile

A constant value, or any path declared in this module.

Returns:
dict

{'kind': <class name>, ...} with the defining numbers as exact hexadecimal float literals and long arrays as SHA-256 digests.

Raises:
TranslationError

If the path is an arbitrary callable (FromCallable) or an implementation this module does not know: neither has a structural identity, and a repr is not one.

Examples

>>> from solprop.profiles import Linear, profile_identity
>>> profile_identity(Linear(125e-6, 75e-6, 1.5))["kind"]
'Linear'
>>> profile_identity(2.0) == profile_identity(2.0)
True