elli-gf2 0.1.0

Machine-first GF(2) elimination library with exact multi-backend reduction and auto-selection.
Documentation
use std::env;
use std::hint::black_box;
use std::process;
use std::time::Instant;

use elli_gf2::{
    Strategy, avg_col_weight, generate_banded, generate_er_sparse, generate_ldpc_like,
    recommended_strategy, reduce_with_strategy,
};

fn usage() -> ! {
    eprintln!(
        "Usage:
  cargo run --release --bin bench_cli -- --family er_sparse --rows 2048 --cols 2048 --density 0.01 --strategy auto
  cargo run --release --bin bench_cli -- --family ldpc_like --rows 2048 --cols 4096 --col-weight 6 --strategy auto
  cargo run --release --bin bench_cli -- --family banded --rows 2048 --cols 2048 --bandwidth 64 --weight 10 --strategy auto
  cargo run --release --bin bench_cli -- --family er_sparse --rows 2048 --cols 2048 --density 0.01 --compare-all

Options:
  --family <er_sparse|ldpc_like|banded>
  --rows <usize>
  --cols <usize>
  --density <f64>        (required for er_sparse)
  --col-weight <usize>   (required for ldpc_like)
  --bandwidth <usize>    (required for banded)
  --weight <usize>       (required for banded)
  --seed <u64>           (default: 1337)
  --strategy <sparse|packed|dense|auto>   (default: auto)
  --reps <usize>         (default: 3)
  --compare-all          (ignore --strategy and print all backends)"
    );
    process::exit(1);
}

fn parse_strategy(s: &str) -> Strategy {
    match s {
        "sparse" => Strategy::SparseList,
        "packed" => Strategy::PackedCached,
        "dense" => Strategy::DenseMacro,
        "auto" => Strategy::Auto,
        _ => {
            eprintln!("unknown strategy: {s}");
            usage();
        }
    }
}

fn median_u128(xs: &mut [u128]) -> u128 {
    xs.sort_unstable();
    let n = xs.len();
    if n % 2 == 0 {
        (xs[n / 2 - 1] + xs[n / 2]) / 2
    } else {
        xs[n / 2]
    }
}

fn time_strategy(rows: usize, cols: &[Vec<u32>], strategy: Strategy, reps: usize) -> (u128, usize) {
    let mut samples = Vec::with_capacity(reps);
    let rank_ref = reduce_with_strategy(rows, cols, strategy).rank;

    for _ in 0..reps {
        let t0 = Instant::now();
        let out = black_box(reduce_with_strategy(rows, cols, strategy));
        samples.push(t0.elapsed().as_micros());
        black_box(out);
    }

    (median_u128(&mut samples), rank_ref)
}

fn main() {
    let mut family: Option<String> = None;
    let mut rows: Option<usize> = None;
    let mut cols_n: Option<usize> = None;
    let mut density: Option<f64> = None;
    let mut col_weight: Option<usize> = None;
    let mut bandwidth: Option<usize> = None;
    let mut weight: Option<usize> = None;
    let mut seed: u64 = 1337;
    let mut reps: usize = 3;
    let mut strategy = Strategy::Auto;
    let mut compare_all = false;

    let mut args = env::args().skip(1).peekable();
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "--family" => family = Some(args.next().unwrap_or_else(|| usage())),
            "--rows" => {
                rows = Some(
                    args.next()
                        .unwrap_or_else(|| usage())
                        .parse()
                        .unwrap_or_else(|_| usage()),
                )
            }
            "--cols" => {
                cols_n = Some(
                    args.next()
                        .unwrap_or_else(|| usage())
                        .parse()
                        .unwrap_or_else(|_| usage()),
                )
            }
            "--density" => {
                density = Some(
                    args.next()
                        .unwrap_or_else(|| usage())
                        .parse()
                        .unwrap_or_else(|_| usage()),
                )
            }
            "--col-weight" => {
                col_weight = Some(
                    args.next()
                        .unwrap_or_else(|| usage())
                        .parse()
                        .unwrap_or_else(|_| usage()),
                )
            }
            "--bandwidth" => {
                bandwidth = Some(
                    args.next()
                        .unwrap_or_else(|| usage())
                        .parse()
                        .unwrap_or_else(|_| usage()),
                )
            }
            "--weight" => {
                weight = Some(
                    args.next()
                        .unwrap_or_else(|| usage())
                        .parse()
                        .unwrap_or_else(|_| usage()),
                )
            }
            "--seed" => {
                seed = args
                    .next()
                    .unwrap_or_else(|| usage())
                    .parse()
                    .unwrap_or_else(|_| usage())
            }
            "--reps" => {
                reps = args
                    .next()
                    .unwrap_or_else(|| usage())
                    .parse()
                    .unwrap_or_else(|_| usage())
            }
            "--strategy" => strategy = parse_strategy(&args.next().unwrap_or_else(|| usage())),
            "--compare-all" => compare_all = true,
            _ => {
                eprintln!("unknown arg: {arg}");
                usage();
            }
        }
    }

    let family = family.unwrap_or_else(|| usage());
    let rows = rows.unwrap_or_else(|| usage());
    let cols_n = cols_n.unwrap_or_else(|| usage());

    let cols = match family.as_str() {
        "er_sparse" => generate_er_sparse(rows, cols_n, density.unwrap_or_else(|| usage()), seed),
        "ldpc_like" => {
            generate_ldpc_like(rows, cols_n, col_weight.unwrap_or_else(|| usage()), seed)
        }
        "banded" => generate_banded(
            rows,
            cols_n,
            bandwidth.unwrap_or_else(|| usage()),
            weight.unwrap_or_else(|| usage()),
            seed,
        ),
        _ => {
            eprintln!("unknown family: {family}");
            usage();
        }
    };

    let avg_w = avg_col_weight(&cols);
    let auto_choice = recommended_strategy(&cols);
    let auto_rank = reduce_with_strategy(rows, &cols, Strategy::Auto).rank;

    println!("family,rows,cols,avg_col_weight,requested_strategy,auto_strategy,elapsed_us,rank");

    if compare_all {
        let order = [
            Strategy::SparseList,
            Strategy::PackedCached,
            Strategy::DenseMacro,
            Strategy::Auto,
        ];

        for strat in order {
            let (elapsed_us, rank) = time_strategy(rows, &cols, strat, reps);
            println!(
                "{},{},{},{:.3},{:?},{:?},{},{}",
                family, rows, cols_n, avg_w, strat, auto_choice, elapsed_us, rank
            );
            if rank != auto_rank {
                eprintln!(
                    "rank mismatch under strategy {:?}: got {}, expected {}",
                    strat, rank, auto_rank
                );
                process::exit(2);
            }
        }
    } else {
        let (elapsed_us, rank) = time_strategy(rows, &cols, strategy, reps);
        println!(
            "{},{},{},{:.3},{:?},{:?},{},{}",
            family, rows, cols_n, avg_w, strategy, auto_choice, elapsed_us, rank
        );
        if rank != auto_rank {
            eprintln!(
                "rank mismatch under strategy {:?}: got {}, expected {}",
                strategy, rank, auto_rank
            );
            process::exit(2);
        }
    }
}