solprop.translate.precision#

The arithmetic a run is held in, and the range rules of single precision (SEP 33).

A run is "double" (binary64, the default and the only arithmetic of numba_cpu and native) or "single": the propagated state, the stage values, every transform and the responses in IEEE binary32, with the controller, every host scalar and the linear propagator’s exponent in binary64 (docs/dev/seps/archive/sep-0033-single-precision-mlx.md section 4.2, ADR-054). This module holds what every backend that admits single precision shares, so that they compute the same exponents and refuse the same things:

Nothing here changes a binary64 run: the translation layer’s tables stay binary64, and a backend narrows them on upload.

Module Attributes

PrecisionName

The public values of precision: binary64 or binary32 arithmetic.

SINGLE_RTOL_FLOOR

The smallest relative tolerance a single-precision run admits (SEP 33, Q1).

SINGLE_PRECISION_BACKENDS

The backends that admit precision="single".

DOUBLE

Binary64 throughout: the default.

SINGLE

Binary32 for the state, the stages and the source evaluation.

Functions

admit_precision(precision, *, backend, ...)

Apply the single-precision refusals every interface and backend shares.

block_exponent(values)

Return the power of two that brings a buffer's largest magnitude into [1, 2).

combine(*factors[, exponent])

Combine scalar factors and power-of-two exponents in binary64.

launch_peak(field0, kind)

Return the peak temporal amplitude of a launch, in the state's own units.

narrow(values)

Narrow a binary64 buffer to binary32 with its block exponent.

pairwise_sum(values[, axis])

Sum along an axis as a balanced binary tree, in the values' own precision.

precision_named(name)

Return the record for a public precision value.

prefix_sum(values[, axis])

Return the running sums along an axis by doubling, in the values' own precision.

scaled_square_sum(values[, weights])

Return sum(w |v|**2) without squaring a value out of range.

state_scale(peak)

Return the exact power-of-two scale of a single-precision run's state.

study_admission()

Admit, inside the block, what the survival study measures and the public refuses.

Classes

Coefficient(mantissa, exponent)

A scalar factor combined on the host as a binary64 mantissa and an exponent.

Narrowed(values, exponent)

A binary64 buffer narrowed to binary32 with its own power-of-two exponent.

Precision([state, source])

The arithmetic of a run's propagated state and of its source evaluation.

class solprop.translate.precision.Coefficient(mantissa, exponent)[source]#

Bases: object

A scalar factor combined on the host as a binary64 mantissa and an exponent.

SEP 33 section 4.3, rule 3: the density, an amplitude, a prefactor’s constant, the state scale’s powers and the buffers’ exponents are combined in binary64 into one coefficient per product, kept as a mantissa in [0.5, 1) (or zero) and a separate integer exponent, so that the combination itself cannot leave binary64’s range whatever the exponents.

Attributes:
mantissafloat

The binary64 mantissa, 0 or of magnitude in [0.5, 1).

exponentint

The power of two it multiplies.

binary32()[source]#

Return the coefficient rounded once to binary32, as a Python float.

Returns:
float

The value of numpy.float32(value).

Raises:
TranslationError

If the coefficient is not a finite normal binary32 number (or zero): it would overflow or lose its precision, so the pass that applies it has not kept its intermediates in range.

exponent: int#
mantissa: float#
property value: float[source]#

Return the coefficient as one binary64 number.

Returns:
float

mantissa * 2**exponent.

Raises:
TranslationError

If the coefficient is outside binary64’s range.

solprop.translate.precision.DOUBLE = Precision(state='double', source='double')#

Binary64 throughout: the default.

class solprop.translate.precision.Narrowed(values, exponent)[source]#

Bases: object

A binary64 buffer narrowed to binary32 with its own power-of-two exponent.

The buffer stands for values * 2**exponent; the exponent is a host integer and is folded into the binary64 coefficient the buffer is multiplied by (combine()), never applied to the binary32 values.

Attributes:
valuesnumpy.ndarray

The scaled buffer, float32 or complex64, read-only, with its largest magnitude in [1, 2) (or zero).

exponentint

The block exponent, block_exponent() of the binary64 buffer.

exponent: int#
values: ndarray#
widened()[source]#

Return the buffer the narrowed one stands for, in binary64.

Returns:
numpy.ndarray

values * 2**exponent, exactly, as float64 or complex128.

class solprop.translate.precision.Precision(state='double', source='double')[source]#

Bases: object

The arithmetic of a run’s propagated state and of its source evaluation.

The public strings map to equal fields: "double" is binary64 throughout and "single" binary32 throughout, with the host scalars, the controller and the propagator’s exponent in binary64 either way. A mixed mode would set the two fields apart and is not admitted.

Attributes:
state{‘double’, ‘single’}

The propagated state, the stage values and the error estimate’s array.

source{‘double’, ‘single’}

The transforms and the responses of a source evaluation.

Examples

>>> from solprop.translate.precision import Precision, precision_named
>>> precision_named("single")
Precision(state='single', source='single')
>>> Precision().name, precision_named("single").single
('double', True)
property complex_dtype: dtype[source]#

Return the dtype of the state and of every complex pass.

Returns:
numpy.dtype

complex64 under single precision, complex128 otherwise.

property name: Literal['double', 'single'][source]#

Return the public value this record stands for.

Returns:
{‘double’, ‘single’}

The shared value of the two fields.

property real_dtype: dtype[source]#

Return the dtype of every real pass.

Returns:
numpy.dtype

float32 under single precision, float64 otherwise.

property single: bool[source]#

Return whether the state is held in binary32.

Returns:
bool

True under precision="single".

source: Literal['double', 'single']#
state: Literal['double', 'single']#
solprop.translate.precision.PrecisionName#

The public values of precision: binary64 or binary32 arithmetic.

alias of Literal[‘double’, ‘single’]

solprop.translate.precision.SINGLE = Precision(state='single', source='single')#

Binary32 for the state, the stages and the source evaluation.

solprop.translate.precision.SINGLE_PRECISION_BACKENDS: tuple[str, ...] = ('numpy_ref', 'mlx')#

The backends that admit precision="single".

numpy_ref is the single-precision oracle (SEP 33 phase 1) and mlx the Apple-GPU backend (phase 2). numba_cpu and native refuse it by name until a later proposal adds their own modes.

solprop.translate.precision.SINGLE_RTOL_FLOOR = 1e-06#

The smallest relative tolerance a single-precision run admits (SEP 33, Q1).

A local error target below the per-step rounding of binary32, about ten times its unit roundoff, buys steps without accuracy. An explicit tolerance below the floor is refused, and the floor is every interface’s default under single precision.

solprop.translate.precision.admit_precision(precision, *, backend, adams, rtol, schedule)[source]#

Apply the single-precision refusals every interface and backend shares.

SEP 33 section 5.2. A binary64 run passes unchanged. A single-precision run is refused on a backend that does not admit it (SINGLE_PRECISION_BACKENDS), with the Adams integrator until the survival study admits it, and with an adaptive tolerance below SINGLE_RTOL_FLOOR. The routes and responses no backend runs in binary32 are refused by validate(), where they are known.

Parameters:
precisionPrecision

The run’s precision.

backendstr

The concrete registry name the run executes on.

adamsbool

Whether the integrator is an Adams method.

rtolfloat or None

The controller’s relative tolerance, or None where the controller has none.

schedulebool

Whether a fixed schedule is replayed, which reads no tolerance.

Raises:
TranslationError

Naming the refusal and what to do instead.

Examples

>>> from solprop.translate.precision import SINGLE, admit_precision
>>> admit_precision(SINGLE, backend="numpy_ref", adams=False, rtol=1e-6,
...                 schedule=False) is None
True
solprop.translate.precision.block_exponent(values)[source]#

Return the power of two that brings a buffer’s largest magnitude into [1, 2).

SEP 33 section 4.3, rule 1: a buffer narrowed to binary32 is stored as values * 2**-e, which is values rounded once, with no range loss for entries within 2**-126 of its largest.

Parameters:
valuesnumpy.ndarray or float

The binary64 buffer, real or complex, finite.

Returns:
int

e with max |values| * 2**-e in [1, 2), or 0 for an all-zero buffer.

Raises:
TranslationError

If the buffer holds a non-finite value.

Examples

>>> from solprop.translate.precision import block_exponent
>>> [block_exponent(x) for x in (1.0, 3.0, 0.25, 0.0)]
[0, 1, -2, 0]
solprop.translate.precision.combine(*factors, exponent=0)[source]#

Combine scalar factors and power-of-two exponents in binary64.

Each factor is split into its mantissa and exponent first, so the product of the mantissas stays in [2**-n, 1) for n factors and the exponents add as integers: a density of 2.5e25 times a kernel exponent of -164 never forms the intermediate 2.5e25 * 2**-164 out of range.

Parameters:
*factorsfloat

Finite binary64 factors.

exponentint, optional

A further power of two, such as a buffer’s block exponent or a power of the state scale’s exponent.

Returns:
Coefficient

The product, normalised.

Examples

>>> from solprop.translate.precision import combine
>>> c = combine(2.5e25, 8.854e-12, exponent=-164)
>>> abs(c.value - 2.5e25 * 8.854e-12 * 2.0**-164) < 1e-60
True
solprop.translate.precision.launch_peak(field0, kind)[source]#

Return the peak temporal amplitude of a launch, in the state’s own units.

The quantity the state scale is chosen from (SEP 33 section 4.3, rule 2): every column of the canonical state transformed to time on its own grid, without padding, in binary64.

Parameters:
field0numpy.ndarray

The launch, complex, in the canonical layout (..., n_omega).

kind{‘field’, ‘envelope’}

The spectral layout.

Returns:
float

The largest temporal magnitude, non-negative.

Examples

>>> import numpy as np
>>> from solprop.translate.precision import launch_peak
>>> launch_peak(np.full((1, 1, 1, 4), 2.0 + 0.0j), "envelope")
2.0
solprop.translate.precision.narrow(values)[source]#

Narrow a binary64 buffer to binary32 with its block exponent.

Multiplying by a power of two is exact in binary64 for these magnitudes, so the narrowed values are the buffer rounded once.

Parameters:
valuesnumpy.ndarray

The binary64 buffer, real or complex, finite.

Returns:
Narrowed

The read-only float32 or complex64 buffer and its exponent.

Examples

>>> import numpy as np
>>> from solprop.translate.precision import narrow
>>> kernel = narrow(np.array([5.2e-50, 1.0e-50]))
>>> kernel.values.dtype, kernel.exponent, bool(np.all(kernel.values > 0))
(dtype('float32'), -164, True)
solprop.translate.precision.pairwise_sum(values, axis=-1)[source]#

Sum along an axis as a balanced binary tree, in the values’ own precision.

The reduction order of the single-precision oracle (SEP 33 section 4.2, Q5): the axis is padded with zeros to a power of two and halved repeatedly, each level one vectorised addition. The error of each sum is bounded by gamma_d times the sum of magnitudes, d = ceil(log2 n) the depth of the tree.

Parameters:
valuesnumpy.ndarray

The terms; the result has the same dtype.

axisint, optional

The axis summed over.

Returns:
numpy.ndarray

The sums, with axis removed.

Examples

>>> import numpy as np
>>> from solprop.translate.precision import pairwise_sum
>>> pairwise_sum(np.arange(5, dtype=np.float32)).item()
10.0
solprop.translate.precision.precision_named(name)[source]#

Return the record for a public precision value.

Parameters:
name{‘double’, ‘single’} or Precision

The public value, or a record, which is returned unchanged.

Returns:
Precision

DOUBLE or SINGLE.

Raises:
TranslationError

If name is not a public value.

Examples

>>> from solprop.translate.precision import precision_named
>>> precision_named("double").name
'double'
solprop.translate.precision.prefix_sum(values, axis=-1)[source]#

Return the running sums along an axis by doubling, in the values’ own precision.

The running-integral order of the single-precision oracle (SEP 33 section 4.2, Q5): ceil(log2 n) vectorised levels, at level k each entry adding the entry 2**k before it (a Hillis-Steele scan). Every prefix is then a sum of depth at most ceil(log2 n), bounded by gamma_d times its sum of magnitudes, where a sequential binary32 sum of 10**4 terms could lose 10**4 u.

Parameters:
valuesnumpy.ndarray

The increments; the result has the same dtype and shape.

axisint, optional

The axis scanned along.

Returns:
numpy.ndarray

out[..., i] = sum(values[..., :i + 1]) along axis.

Examples

>>> import numpy as np
>>> from solprop.translate.precision import prefix_sum
>>> prefix_sum(np.arange(5, dtype=np.float32)).tolist()
[0.0, 1.0, 3.0, 6.0, 10.0]
solprop.translate.precision.scaled_square_sum(values, weights=None)[source]#

Return sum(w |v|**2) without squaring a value out of range.

SEP 33 section 4.3, rule 6: the values are divided by a power of two chosen from their largest magnitude before squaring, as LAPACK’s nrm2 does, the squares summed in the values’ precision by pairwise_sum(), and the scale restored on the host in binary64.

Parameters:
valuesnumpy.ndarray

The values, real or complex, of any precision.

weightsnumpy.ndarray or None, optional

Non-negative weights broadcastable against values, in the values’ real precision; None weighs every value by one.

Returns:
float

The weighted sum of squares, binary64.

Examples

>>> import numpy as np
>>> from solprop.translate.precision import scaled_square_sum
>>> big = np.full(4, 3.0e30, dtype=np.float32)
>>> total = scaled_square_sum(big)
>>> abs(total / (4 * float(np.float32(3.0e30)) ** 2) - 1.0) < 1e-6
True
solprop.translate.precision.state_scale(peak)[source]#

Return the exact power-of-two scale of a single-precision run’s state.

SEP 33 section 4.3, rule 2: the state is held as a / s with s = 2**k chosen from the launch so that the launch’s peak temporal amplitude over s lies in [1, 2); s = 1 for an identically zero launch. Being a function of the problem, not of the state at set_initial, it is the same for a run, its restarts and its resumes.

Parameters:
peakfloat

The launch’s peak temporal amplitude, in the state’s units, non-negative.

Returns:
float

2**k.

Examples

>>> from solprop.translate.precision import state_scale
>>> state_scale(3.0e4), state_scale(0.0)
(16384.0, 1.0)
solprop.translate.precision.study_admission()[source]#

Admit, inside the block, what the survival study measures and the public refuses.

The private, test-only route of SEP 33 section 5.2: the study runs the Adams integrator under single precision through it, to measure whether Adams can be admitted (Q2). No public interface sets it.

Yields:
None

Nothing; the admission is relaxed until the block exits.