Skip to main content

zip_reduce

Function zip_reduce 

Source
pub fn zip_reduce<A, B, OpA, OpB, M, R, U>(
    a: &StridedView<'_, A, OpA>,
    b: &StridedView<'_, B, OpB>,
    map_fn: M,
    reduce_fn: R,
    init: U,
    exec: Exec<'_>,
) -> Result<U>
where A: Copy + Send + Sync, B: Copy + Send + Sync, OpA: ElementOp<A>, OpB: ElementOp<B>, M: Fn(A, B) -> U + Sync, R: Fn(U, U) -> U + Sync, U: Clone + Send + Sync,
Expand description

Two-input mapped reduction: reduce(init, op, map.(a[i], b[i])).

This is the two-operand sibling of reduce. It exists so that the accumulator type U is chosen by the caller instead of being tied to the operand types by Mul, which is what makes a widening accumulation possible — for example an f32 dot accumulated in f64:

use strided_basic::{zip_reduce, Exec, StridedArray};

let a = StridedArray::<f32>::from_fn_col_major(&[2, 3], |i| (1 + i[0] + 2 * i[1]) as f32);
let b = StridedArray::<f32>::from_fn_col_major(&[2, 3], |i| (1 + i[0]) as f32 * 0.5);
let dot: f64 = zip_reduce(
    &a.view(),
    &b.view(),
    |x: f32, y: f32| x as f64 * y as f64,
    |x, y| x + y,
    0.0,
    Exec::serial(),
)
.unwrap();
assert_eq!(dot, 16.5);

Both operands must have the same shape. Each operand’s ElementOp is applied before map_fn, so conjugation and transposition flags are honoured without materializing a conjugated copy. Permuted, gapped, negative and stride-0 (broadcast) operands are supported: the traversal is the fused plan of reduce, extended to two stride lists.

§Evaluation order

The result is independent of the pool when the call runs with a serial token (Exec::serial) or a budget of one: only serial branches run, and their fold order is fixed by the operand layouts. With a larger budget the fold is split over the budget, so the result is repeatable for a fixed shape, layout and budget but can differ between budgets.

In neither case is this a strict left to right fold: a contiguous operand pair is folded with the same sixteen independent lanes as reduce, the general plan is traversed in a cache-friendly loop order, and parallel execution folds per-worker chunks independently and combines them in a tree. Floating point results can therefore differ from a sequential left to right sum in the last bits, and no bitwise equality across machines or target features is guaranteed.

Because of that, reduce_fn must be associative and commutative, and init must be an identity of reduce_fn whenever the reduction can run on several threads.

§Allocation

At rank ≤ INLINE_RANK the view metadata and the two-operand traversal plan are stored inline, so a call performs no heap allocation. Above that bound the planner may spill to the heap.

§Errors

Returns StridedError::RankMismatch / StridedError::ShapeMismatch when the operand shapes differ, and StridedError::OffsetOverflow for unrepresentable element counts.