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:
Precision, the record aKernelProblemcarries, with named fields so that a later mixed mode (a binary32 source beside a binary64 state) is a third public value rather than a schema redesign;admit_precision(), the shared admission of the defaults and refusals;the range helpers of SEP 33 section 4.3:
block_exponent()andnarrow()for every table narrowed at upload,state_scale()for the state’s exact power-of-two scale,Coefficientandcombine()for the scalar factors combined on the host, andpairwise_sum(),prefix_sum()andscaled_square_sum(), the reduction orders the single-precision oracle defines (SEP 33, Q5).
Nothing here changes a binary64 run: the translation layer’s tables stay binary64, and a backend narrows them on upload.
Module Attributes
The public values of |
|
The smallest relative tolerance a single-precision run admits (SEP 33, Q1). |
|
The backends that admit |
|
Binary64 throughout: the default. |
|
Binary32 for the state, the stages and the source evaluation. |
Functions
|
Apply the single-precision refusals every interface and backend shares. |
|
Return the power of two that brings a buffer's largest magnitude into [1, 2). |
|
Combine scalar factors and power-of-two exponents in binary64. |
|
Return the peak temporal amplitude of a launch, in the state's own units. |
|
Narrow a binary64 buffer to binary32 with its block exponent. |
|
Sum along an axis as a balanced binary tree, in the values' own precision. |
|
Return the record for a public precision value. |
|
Return the running sums along an axis by doubling, in the values' own precision. |
|
Return |
|
Return the exact power-of-two scale of a single-precision run's state. |
Admit, inside the block, what the survival study measures and the public refuses. |
Classes
|
A scalar factor combined on the host as a binary64 mantissa and an exponent. |
|
A binary64 buffer narrowed to binary32 with its own power-of-two exponent. |
|
The arithmetic of a run's propagated state and of its source evaluation. |
- class solprop.translate.precision.Coefficient(mantissa, exponent)[source]#
Bases:
objectA 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,
0or 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.
- solprop.translate.precision.DOUBLE = Precision(state='double', source='double')#
Binary64 throughout: the default.
- class solprop.translate.precision.Narrowed(values, exponent)[source]#
Bases:
objectA 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,
float32orcomplex64, read-only, with its largest magnitude in[1, 2)(or zero).- exponentint
The block exponent,
block_exponent()of the binary64 buffer.
- class solprop.translate.precision.Precision(state='double', source='double')[source]#
Bases:
objectThe 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
complex64under single precision,complex128otherwise.
- 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
float32under single precision,float64otherwise.
- 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_refis the single-precision oracle (SEP 33 phase 1) andmlxthe Apple-GPU backend (phase 2).numba_cpuandnativerefuse 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 belowSINGLE_RTOL_FLOOR. The routes and responses no backend runs in binary32 are refused byvalidate(), 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
Nonewhere 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 within2**-126of its largest.- Parameters:
- valuesnumpy.ndarray or float
The binary64 buffer, real or complex, finite.
- Returns:
- int
ewithmax |values| * 2**-ein[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)fornfactors and the exponents add as integers: a density of2.5e25times a kernel exponent of-164never forms the intermediate2.5e25 * 2**-164out 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
float32orcomplex64buffer 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_dtimes 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:
- 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 levelkeach entry adding the entry2**kbefore it (a Hillis-Steele scan). Every prefix is then a sum of depth at mostceil(log2 n), bounded bygamma_dtimes its sum of magnitudes, where a sequential binary32 sum of10**4terms could lose10**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
nrm2does, the squares summed in the values’ precision bypairwise_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 / swiths = 2**kchosen from the launch so that the launch’s peak temporal amplitude overslies in[1, 2);s = 1for an identically zero launch. Being a function of the problem, not of the state atset_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.