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,
) -> Result<U>
where A: Copy + MaybeSendSync, B: Copy + MaybeSendSync, OpA: ElementOp<A>, OpB: ElementOp<B>, M: Fn(A, B) -> U + MaybeSync, R: Fn(U, U) -> U + MaybeSync, U: Clone + MaybeSendSync,
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, 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,
)
.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 ambient pool width when the call runs under the sequential policy — ExecContext::serial or with_execution_policy with ExecutionPolicy::Sequential: only serial branches run, and their fold order is fixed by the operand layouts. Under a parallel policy the fold is split over the effective thread count, so the result is repeatable for a fixed shape, layout and thread count but can differ between thread counts.

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.