Built-in low-precision types

Pychop ships four ready-made classes that automatically chop to the desired format after each public operation boundary:

All four work with any Chop (or FaultChop) instance and keep the result inside the low-precision type when the wrapped operation returns a floating-point or complex value. Integer and Boolean outputs, such as pivot indices and comparison masks, are left as ordinary backend values.

Note

The built-in types chop after Python/NumPy/PyTorch/JAX operation calls. They do not chop every internal multiply/add inside fused BLAS, LAPACK, XLA, or PyTorch kernels. For strict per-arithmetic-step simulation, write the algorithm steps explicitly with the built-in types or use a specialized chopped kernel.

Quick import

import pychop
from pychop import Chop
from pychop.builtin import CPFloat, CPArray, CPJaxArray, CPTensor, cast_precision

pychop.backend('torch') # use 'numpy', 'jax', or 'torch' for the matching container

Common set-up

# Half-precision (IEEE-754 binary16) – subnormals enabled
half = Chop(exp_bits=5, sig_bits=10, subnormal=True, rmode=1)

# Under-flow-free half (tiny numbers become zero)
ufhalf = Chop(exp_bits=5, sig_bits=10, subnormal=False, rmode=1)

Scalar – CPFloat

class pychop.builtin.CPFloat(value: int | float | complex | number, chopper: Any)[source]

Bases: object

Chopped-precision scalar.

Parameters

valueint, float, complex, or numpy scalar

Input scalar value. It is chopped immediately at construction time.

choppercallable

A Chop-like object. It must be callable and accept a backend-appropriate scalar container: - NumPy backend: expects a NumPy array/scalar - Torch backend: expects a torch.Tensor - JAX backend: expects a jax.Array

Attributes

valuefloat

The chopped value stored as a Python scalar (typically float, but can be complex depending on backend and operation).

choppercallable

The chopping/quantization operator.

Examples

Basic arithmetic stays chopped:

from pychop import Chop
from pychop.builtin import CPFloat

half = Chop(exp_bits=5, sig_bits=10, subnormal=True, rmode=1)

a = CPFloat(1.234567, half)
b = CPFloat(0.987654, half)

c = a + b
d = a * b / 2.0 - 0.1

NumPy ufunc interoperability:

import numpy as np
x = CPFloat(1.234, half)

y = np.sin(x)          # -> CPFloat
z = np.sqrt(x + 1.0)   # -> CPFloat

Notes

  • Mixed-chopper operations are disallowed for CPFloat binary arithmetic and NumPy ufuncs.

  • For torch/jax specific functions (e.g., torch.sin), CPFloat will be coerced to a Python float unless you build explicit wrappers.

astype_precision(chopper: Any) CPFloat[source]

Cast this scalar to another chopped precision.

The returned scalar is chopped immediately with chopper.

chopper: Any
item()[source]

Return the chopped value as a Python scalar.

Returns

scalar

The stored chopped scalar.

to_numpy(dtype=None) ndarray[source]

Convert to a NumPy 0-d array.

Parameters

dtypenumpy dtype, default=None

Optional dtype to cast to.

Returns

numpy.ndarray

A 0-d NumPy array containing the chopped value.

value: Any

Example

half = Chop(exp_bits=5, sig_bits=10, subnormal=True, rmode=1)
ufhalf = Chop(exp_bits=5, sig_bits=10, subnormal=False, rmode=1)

a = CPFloat(1.234567, half)
b = CPFloat(0.987654, half)

print(a)                     # CPFloat(1.23438, prec=half)
c = a + b                    # stays a CPFloat, chopped
print(c)                     # CPFloat(2.22203, prec=half)
d = a * b / 2.0
print(d)                     # CPFloat(0.609863, prec=half)

# mixed with a normal Python float
e = a + 3.14
print(e)                     # CPFloat(4.37438, prec=half)

PyTorch – CPTensor

Example

import torch
from pychop.builtin import CPTensor

pychop.backend('torch')  # Switch to Torch backend
half = Chop(exp_bits=5, sig_bits=10, subnormal=True, rmode=1)
ufhalf = Chop(exp_bits=5, sig_bits=10, subnormal=False, rmode=1)

x = CPTensor(torch.tensor([1.1, 2.2, 3.3]), half)
y = CPTensor(torch.tensor([0.5, 1.5, 2.5]), half)

print(x)                     # CPTensor(tensor([1.1, 2.2, 3.3]), device=cpu, prec=half)
z = x + y
print(z)                     # CPTensor(tensor([1.6, 3.7, 5.8]), device=cpu, prec=half)

# broadcasting with a plain tensor
reg = torch.tensor([10.0, 20.0, 30.0])
w = x * reg
print(w)                     # CPTensor(tensor([11.0, 44.0, 99.0]), device=cpu, prec=half)

# matrix multiplication
A = CPTensor(torch.randn(4, 3), half)
B = CPTensor(torch.randn(3, 5), half)
C = A @ B
print(C.shape)               # torch.Size([4, 5])

# GPU works out-of-the-box
if torch.cuda.is_available():
   A = A.to('cuda')
   B = B.to('cuda')
   C = A @ B
   print(C.device)          # cuda:0

NumPy – CPArray

class pychop.builtin.CPArray(input_array, chopper=None)[source]

Bases: ndarray

A NumPy array subclass that maintains chopped precision after arithmetic ops.

Key behaviors: - Construction chops the input immediately. - NumPy ufuncs (+, -, *, /, etc.) are intercepted via __array_ufunc__:

compute on base ndarrays -> chop result -> wrap as CPArray.

  • Matrix multiplication (@) is intercepted via __matmul__/__rmatmul__:

    compute with np.matmul on base ndarrays -> wrap (constructor chops).

  • NumPy high-level functions (including np.linalg.*) are intercepted via __array_function__:

    unwrap CPArray inputs to base ndarrays -> call func -> wrap numeric ndarray outputs back to CPArray and chop numeric scalar outputs.

Important safety notes: - __array_function__ MUST be conservative to avoid breaking NumPy internals

(printing/formatting, string/object dtypes, etc.).

  • We only chop/wrap numeric ndarrays (dtype.kind in “biufc”) and numeric scalars.

astype_precision(chopper)[source]

Cast this array to another chopped precision immediately.

to_regular()[source]

Return as a regular NumPy ndarray (drops CPArray subclass).

Example

import numpy as np

half = Chop(exp_bits=5, sig_bits=10, subnormal=True, rmode=1)
ufhalf = Chop(exp_bits=5, sig_bits=10, subnormal=False, rmode=1)
p = CPArray(np.array([10.0, 20.0, 30.0]), half)
q = CPArray(np.array([1.0, 2.0, 3.0]), half)

print(p)                     # CPArray([10. 20. 30.], prec=half)
r = p - q
print(r)                     # CPArray([ 9. 18. 27.], prec=half)

# element-wise with a normal ndarray
plain = np.array([0.5, 1.5, 2.5])
s = p * plain
print(s)                     # CPArray([ 5. 30. 75.], prec=half)

# linear-algebra (still chopped)
M = CPArray(np.random.rand(3, 4), half)
N = CPArray(np.random.rand(4, 2), half)
P = M @ N
print(P.shape)               # (3, 2)

JAX – CPJaxArray

Example

from pychop.builtin import CPJaxArray

pychop.backend('jax')  # Switch to JAX backend
half = Chop(exp_bits=5, sig_bits=10, subnormal=True, rmode=1)
ufhalf = Chop(exp_bits=5, sig_bits=10, subnormal=False, rmode=1)

x = CPJaxArray(jnp.array([1.1, 2.2, 3.3]), half)
y = CPJaxArray(jnp.array([0.5, 1.5, 2.5]), half)
print(x)                     # CPJaxArray([1.1, 2.2, 3.3], prec=half)
z = x + y
print(z)                     # CPJaxArray([1.6, 3.7, 5.8], prec=half)
# broadcasting with a plain array
reg = jnp.array([10.0, 20.0, 30.0])
w = x * reg
print(w)                     # CPJaxArray([11.0, 44.0, 99.0], prec=half)
# matrix multiplication

gpu_devices = jax.devices('gpu')
if gpu_devices:
   with jax.default_device(gpu_devices[0]):

      A = CPJaxArray(jax.random.normal(jax.random.PRNGKey(0), (4, 3)), half)
      B = CPJaxArray(jax.random.normal(jax.random.PRNGKey(1), (3, 5)), half)
      C = A @ B
      print(C.shape)               # (4, 5)
      # GPU works out-of-the-box (JAX auto-dispatches)
      print(C.to_regular().devices())  # {CpuDevice()} or {CudaDevice()}

Under-flow-free (UF) formats

Just create a Chop with subnormal=False and pass it to any of the four types:

uf = Chop(exp_bits=5, sig_bits=10, subnormal=False, rmode=1)

tiny = CPFloat(1e-40, uf)          # becomes 0.0 (flushed)
print(tiny)                        # CPFloat(0.0, prec=uf)

huge = CPTensor(torch.tensor([1e30, 1e35]), uf)
print(huge)                        # CPTensor(tensor([1.0000e+30, inf]), ...)

Supported operations

All Python arithmetic operators (+ * / // % **) and many library functions are dispatched through the wrapper/subclass machinery:

  • NumPy – any ufunc (np.sin, np.exp, np.linalg.norm …)

  • JAX – operators implemented by CPJaxArray; use pychop.builtin.linalg or chopwrap for wrapped high-level JAX outputs

  • PyTorch – any torch.* function (torch.nn.functional.relu, torch.matmul, torch.conv2d …)

Floating-point and complex results are chopped and returned as the matching built-in type. Non-floating outputs are returned as ordinary backend values.

Note

Scalar-returning wrappers in pychop.builtin.linalg return CPFloat where scalar chaining is useful. Backend-native reductions may return backend scalars or arrays depending on the library.

Switching precision

Use pychop.builtin.cast_precision() to switch a value to a new Chop configuration. Casting chops immediately and preserves the container family.

pychop.builtin.cast_precision(x: Any, chopper: Any) Any[source]

Cast a value to a chopped-precision container using chopper.

Existing CPFloat, CPArray, CPJaxArray, and CPTensor inputs keep their container family. Native arrays/tensors are wrapped according to the active backend, or by auto-detecting the input type when the backend is "auto". The cast chops immediately.

fp8 = Chop(exp_bits=4, sig_bits=3, subnormal=True, rmode=1)
x_fp8 = cast_precision(x, fp8)

Pickling / serialization

The NumPy and PyTorch built-in containers implement __reduce_ex__ and can be pickled/unpickled with the usual pickle module.

import pickle, io

buf = io.BytesIO()
pickle.dump(a, buf)          # a is a CPFloat
buf.seek(0)
a2 = pickle.load(buf)
print(a2)                    # same value & chopper

Performance tip

  • Use the PyTorch backend (pychop.backend('torch')) for GPU-accelerated chopping.

  • Use the TensorFlow backend (pychop.backend('tensorflow')) for TensorFlow/Keras workflows with STE-based gradient support. TensorFlow does not currently have a built-in CP* wrapper type.

  • Use the NumPy backend (default) for pure-CPU workloads.

That’s it, simply drop the imports into your code and you get type-preserving low-precision arithmetic for scalars, NumPy arrays, JAX arrays, and PyTorch tensors.