Computing and summing slices

Enumerating slice values, executing in parallel, and combining results, including the effect of slicing on total work and memory.

On this page

Compute each slice, then add the results

Array computation starts only after the path and sliced indices are determined. Each combination of index values defines a subtask: extract the corresponding input data and follow the planned contraction path to produce a slice result. After all slices finish, add their results elementwise to obtain the contraction of the original network.

For example, slicing an internal index of dimension 2 produces two results, R0R_0 and R1R_1, with final result R=R0+R1R=R_0+R_1. This executes the slicing scheme; it does not search for another path.

Value combinations for two sliced indices. Consecutive IDs form a group; slices are accumulated sequentially within each group, groups run in parallel, and results are combined in group-ID order.
rust
use arctn::contract_network_sliced;

let output = contract_network_sliced(
    &net, &tensors, &ssa_path, &sliced_legs
)?;

Parallel computation and result summation

The native executor divides consecutively numbered slices into chunks. Each chunk accumulates in slice-ID order, and Rayon processes different chunks in parallel. Partial sums are then merged in chunk-ID order. Chunking depends only on the slice count and output element count, not the thread count.

Item Behavior
Maximum chunk count At most 256, further limited by the partial-output element budget
Changing thread count Changes when chunks finish, but not their grouping or final merge order
No sliced indices Calls contract_network directly
Concurrent memory Each simultaneously executing slice has its own input copies and intermediates

The thread-count-independent merge order stabilizes single-machine floating-point results. It does not imply that MPI Allreduce is bitwise identical across different rank counts.

Total work and memory

For NN slices, the binary contraction work across all slices is

Ftotal=NFslice,log10Ftotal=log10Fslice+log10N.\begin{aligned} F_{\mathrm{total}}&=NF_{\mathrm{slice}},\\ \log_{10}F_{\mathrm{total}}&=\log_{10}F_{\mathrm{slice}}+\log_{10}N. \end{aligned}

Slicing may reduce the largest per-slice result while increasing total FLOPs through repeated computation. The model excludes extra additions in the final cross-slice summation.

Usually decreases with more slicing Usually increases with more slicing
Largest per-slice intermediate Number of slices in a complete execution
Path-model memory per subtask Total FLOPs and input reads

target_size limits only per-slice binary contraction results; for single-tensor networks it checks the final output. Original inputs, unary preprocessing, transpose temporaries, backend workspace, partial sums, and other concurrently executing slices all increase actual RSS. Execution parallelism must also be considered when controlling total memory.

Three execution routes

Route Slice scheduling Per-slice contraction Result accumulation
Rust / Python native Fixed chunks in Rust, parallelized with Rayon ArcTN Rust CPU executor Accumulation in ID order both within and between chunks
Python external backend Serial enumeration in a fixed order in Python Reuses one opt_einsum.contract_expression Addition on the selected array backend
tnmpi Each process executes a different contiguous slice range ArcTN Rust CPU executor; --dtype supports four real / complex types; see MPI execution MPI Allreduce

All three routes use the same mathematical decomposition, but scheduling, synchronization, and floating-point reduction order differ. State the execution route when reporting performance.