mixle.engines.symbolic_engine module

A small dependency-free symbolic compute engine.

The symbolic engine is for expression tracing, generated-kernel inspection, and lightweight algebraic experiments. Numeric execution remains the job of NumPy/Torch engines; arrays here are NumPy object arrays of scalar expression nodes so generated kernels can be inspected without a separate runtime.

class SymbolicExpression(op, args=())[source]

Bases: object

A compact immutable symbolic expression tree.

Parameters:
static symbol(name)[source]

Create a named symbolic variable node.

Parameters:

name (str)

Return type:

SymbolicExpression

static constant(value)[source]

Create a symbolic constant node.

Parameters:

value (Any)

Return type:

SymbolicExpression

static call(op, *args)[source]

Create an operation node with symbolic arguments.

Parameters:
Return type:

SymbolicExpression

evaluate(values)[source]

Evaluate the expression with a mapping from symbol names to values.

A where node with a SCALAR condition evaluates only the selected branch (MXR-080-1565); see _evaluate_where(). Every other node evaluates all of its arguments.

Parameters:

values (dict[str, Any])

Return type:

Any

symbols()[source]

Return sorted symbolic variable names referenced by this expression.

Return type:

tuple[str, …]

op_counts()[source]

Return operation counts for this expression tree.

Return type:

dict[str, int]

depth()[source]

Return expression-tree depth, counting this node.

Return type:

int

node_count()[source]

Return the total number of expression nodes in this tree.

Return type:

int

class SymbolicEngine[source]

Bases: ComputeEngine

Small symbolic expression engine over scalar nodes and object arrays.

with_precision(precision)[source]

Return this engine unchanged: symbolic expressions carry no float precision policy.

The numeric engines swap their float dtype here, but symbolic nodes are exact expression trees with no reduced-precision representation, so precision adjustment is a no-op rather than an error – this lets backend-neutral code call with_precision uniformly across engines.

Parameters:

precision (Any)

Return type:

SymbolicEngine

constant(value)[source]

Return value as a symbolic constant node.

Parameters:

value (Any)

Return type:

SymbolicExpression

symbol(name)[source]

Return a named symbolic expression variable.

Parameters:

name (str)

Return type:

SymbolicExpression

asarray(x, dtype=None)[source]

Convert scalars/arrays/strings into symbolic expression objects.

Parameters:
Return type:

Any

zeros(shape, dtype=None)[source]

Return an object array filled with symbolic zero constants.

Parameters:
Return type:

Any

empty(shape, dtype=None)[source]

Return an uninitialized object array for symbolic expressions.

Parameters:
Return type:

Any

arange(*args, **kwargs)[source]

Return symbolic constants corresponding to np.arange values.

Parameters:
Return type:

Any

to_numpy(x)[source]

Return x as a NumPy object array without numeric evaluation.

Parameters:

x (Any)

Return type:

Any

evaluate(x, values)[source]

Evaluate a scalar expression or object-array expression tree.

Parameters:
Return type:

Any

symbols(x)[source]

Return sorted symbolic variable names referenced by x.

Parameters:

x (Any)

Return type:

tuple[str, …]

op_counts(x)[source]

Return aggregate operation counts over a scalar or array expression.

Parameters:

x (Any)

Return type:

dict[str, int]

diagnostics(x)[source]

Return a compact diagnostic summary for generated-kernel inspection.

Parameters:

x (Any)

Return type:

dict[str, Any]

stack(arrays, axis=0)[source]

Stack symbolic arrays with NumPy object-array semantics.

Parameters:
Return type:

Any

concatenate(arrays, axis=0)[source]

Join symbolic arrays with NumPy object-array semantics.

Parameters:
Return type:

Any

static sum(x, axis=None, dtype=None, keepdims=False, initial=None, where=None)[source]

Return a symbolic sum reduction over axis, matching NumPy’s sum contract.

keepdims, initial, and where are implemented exactly as np.sum defines them: reduced axes are kept as size-1 dimensions, initial seeds each reduction group (the additive identity 0.0 when omitted), and where excludes masked-out elements by substituting the additive identity in their place before folding. dtype has no symbolic analogue – an expression node carries no numeric storage to cast – so a non-None value is rejected rather than silently ignored (matching the “reject unsupported arguments” half of the declared reduction contract, since honoring it is not meaningful here).

Parameters:
Return type:

Any

static logsumexp(x, axis=None, keepdims=False)[source]

Return a symbolic log-sum-exp reduction over axis, matching SciPy’s logsumexp.

keepdims is implemented exactly as scipy.special.logsumexp defines it. SciPy’s own logsumexp (which NumpyEngine.logsumexp forwards to directly) has no dtype/initial/where parameter either, so there is no declared behavior to honor for them here; passing them raises the same TypeError SciPy’s own function would, for the same reason (an unexpected keyword argument). An all--inf input (every term has zero probability in log-space) correctly evaluates to -inf, not NaN – see _logsumexp_values(). An empty reduction is likewise defined as -inf (the identity: the log of a sum of zero terms), matching SciPy.

Parameters:
Return type:

Any

static max(x, axis=None, keepdims=False, initial=None, where=None)[source]

Return a symbolic max reduction over axis, matching NumPy’s max contract.

keepdims and initial are implemented exactly as np.max defines them (np.max itself has no dtype parameter, so none is declared here either). Max has no universal identity element, so – exactly like np.max – passing where without also passing initial is rejected rather than silently reducing an under-specified group. An empty reduction with no initial raises (there is nothing to seed it with), matching np.max; with initial it returns initial.

Parameters:
Return type:

Any

static where(cond, x, y)[source]

Return a symbolic elementwise conditional expression.

Parameters:
Return type:

Any

static maximum(x, y)[source]

Return a symbolic elementwise maximum expression.

Parameters:
Return type:

Any

static less(x, y)[source]

Return a symbolic less-than comparison.

Parameters:
Return type:

Any

static less_equal(x, y)[source]

Return a symbolic less-than-or-equal comparison.

Parameters:
Return type:

Any

static greater(x, y)[source]

Return a symbolic greater-than comparison.

Parameters:
Return type:

Any

static greater_equal(x, y)[source]

Return a symbolic greater-than-or-equal comparison.

Parameters:
Return type:

Any

static equal(x, y)[source]

Return a symbolic equality comparison.

Parameters:
Return type:

Any

static not_equal(x, y)[source]

Return a symbolic inequality comparison.

Parameters:
Return type:

Any

static logical_and(x, y)[source]

Return a symbolic elementwise logical-and expression.

Parameters:
Return type:

Any

static logical_or(x, y)[source]

Return a symbolic elementwise logical-or expression.

Parameters:
Return type:

Any

static logical_not(x)[source]

Return a symbolic elementwise logical-not expression.

Parameters:

x (Any)

Return type:

Any

static clip(x, a_min=None, a_max=None)[source]

Return a symbolic clipped expression.

Parameters:
Return type:

Any

static cumsum(x, axis=None, dtype=None, out=None)[source]

Return a symbolic cumulative sum over axis, matching NumPy’s cumsum contract.

axis behaves exactly as np.cumsum defines it (None flattens first). dtype has no symbolic analogue – an expression node carries no numeric storage to cast into, the same reason sum() rejects it – and out cannot be honored either, because the result is a freshly built object array of expression nodes rather than a buffer that can be written into. Both are therefore rejected rather than silently discarded (MXR-080-1567: the previous implementation was a lambda that swallowed dtype, out, and any other argument into unused *args/**kwargs and passed only axis through, so a caller asking for a float32 accumulation or an output buffer got neither and no signal). An unexpected keyword now raises TypeError from ordinary signature binding, as the other engines’ cumsum does.

Parameters:
Return type:

Any

index_add(out, index, values)[source]

Return a symbolic index-add operation node.

Parameters:
Return type:

Any

static to_sympy(x)[source]

Lower a symbolic expression (or object array) to a sympy expression.

Parameters:

x (Any)

Return type:

Any

static to_sage(x)[source]

Lower a symbolic expression (or object array) to a sage expression.

Parameters:

x (Any)

Return type:

Any

static to_latex(x)[source]

Return a LaTeX string for a symbolic expression via sympy.

Parameters:

x (Any)

Return type:

str

SYMBOLIC_ENGINE = <mixle.engines.symbolic_engine.SymbolicEngine object>

Shared symbolic engine; arithmetic on symbolic nodes/object arrays dispatches here.

is_symbolic_payload(x)[source]

Return True for a symbolic node or a NumPy object array of ALL-symbolic nodes.

Every element of an object array is checked, not just the first: classifying the whole array from a single element lets a mixed-ownership payload – some elements genuine SymbolicExpression nodes, others unrelated plain objects – be misrouted based on whichever kind happens to occupy index 0 (numeric-first routes the symbolic elements through NumPy, which was never built to handle them; symbolic-first routes the unrelated elements through the symbolic engine instead). Mixed ownership is rejected outright rather than silently guessed at from one element.

Scans left to right and stops at the first element whose kind disagrees with the first element’s, so a genuinely uniform array (the overwhelmingly common case) still only pays for a single early-exit scan, while a mixed one is still caught regardless of which kind happens to sit at index 0.

Parameters:

x (Any)

Return type:

bool