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>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.