mixle.engines.arithmetic module¶
Backend-dispatched arithmetic helpers used by mixle classes.
Array operations dispatch on their arguments’ engine (engine_of); scalar constants have no
arguments to dispatch on, so they resolve from the active engine instead – NUMPY_ENGINE by
default (plain floats, the historical behaviour). Select a different one to make the constants come
from it, e.g. the symbolic engine, so that pi stays a symbolic pi and half an exact 1/2
instead of collapsing to floats during exact arithmetic:
import mixle.engines.arithmetic as arith
with arith.using_engine("symbolic"):
arith.pi # SymbolicExpression "pi", not 3.141592...
This is the extension point for letting users control the arithmetic backend without switching mixle to a
full computer-algebra system. maxint / maxrandint / eps are implementation limits and
tolerances (not mathematical constants), so they stay engine-independent.
- to_numpy(x)[source]
Convert an engine array/tensor payload to NumPy at an explicit boundary.
Deliberately NOT built via
_dispatch()like the other allocation wrappers above:_dispatchresolves ONE engine for the whole payload (correctly, recursing throughengine_of()) but then hands that single engine the ORIGINAL, unconverted container – which fails to stack a ragged list of arrays, leaves dict values unconverted (NumpyEngine.to_numpy->np.asarrayon a dict wraps it in a useless 0-d object array instead of touching its values), and can hand a device tensor to a host conversion without transferring it first. This is exactly the bugmixle.engines.to_numpy()was fixed for (MXR-080-0123): a dict/list/tuple that CONTAINS an engine-owned value is converted leaf by leaf here too, preserving container structure and resolving each leaf’s own owning engine independently.This is intentionally NOT a direct alias of the now-fixed
mixle.engines.to_numpy()– there is no import-cycle reason to avoid that (mixle.enginesdoes not import this module at all, only the reverse). That free function’s “no engine-owned value anywhere” fallback is hardcoded toNUMPY_ENGINE, while this whole module’s documented purpose (see the module docstring) is resolving exactly that fallback against the ACTIVE engine (_DEFAULT_ENGINE, e.g. a caller insideusing_engine("symbolic")) – aliasing the package-level function directly would silently drop that contract for plain, non-engine-owned data. The container- walking shape below is intentionally the same recursion asmixle.engines.to_numpy()(reusing its private_contains_engine_valueleaf-ownership test rather than re-deriving it, so this stays in sync with any future fix to engine-ownership detection there); only the final leaf’s fallback engine differs.
- constant(value)[source]
Wrap
valuein the active engine’s scalar representation (identity for numeric engines).
- get_default_engine()[source]
Return the engine that supplies scalar constants and the operation-dispatch default.
- Return type:
ComputeEngine
- set_default_engine(engine)[source]
Set the active engine (a
ComputeEngineor"numpy"/"symbolic"); return the previous one.This is a direct, unscoped assignment: like
using_engine, it lives in aContextVarso it is isolated per thread/task, but there is no token to auto-restore – the caller (or a test’s isolation fixture) is responsible for putting the previous engine back. Preferusing_enginefor a scoped override that always restores itself, including when the block raises.- Parameters:
engine (ComputeEngine | str)
- Return type:
ComputeEngine