#![cfg(feature = "gpu")]
use haystackfm::alphabet::{concatenate_sequences, DnaSequence, ALPHABET_SIZE};
use haystackfm::bwt::{cpu::build_bwt as cpu_bwt, gpu::BwtPipelines};
use haystackfm::gpu::GpuContext;
use haystackfm::occ::{cpu::build_occ_table as cpu_occ, gpu::OccPipelines, OccEncoding};
use haystackfm::suffix_array::{cpu::build_suffix_array as cpu_sa, gpu::SaPipelines};
fn get_ctx() -> Option<GpuContext> {
pollster::block_on(GpuContext::new()).ok()
}
fn encode(dna: &str) -> Vec<u8> {
let seq = DnaSequence::from_str(dna).unwrap();
let (text, _) = concatenate_sequences(&[seq]).unwrap();
text
}
fn random_dna(len: usize) -> String {
use rand::Rng;
let mut rng = rand::rng();
let bases = ['A', 'C', 'G', 'T'];
(0..len).map(|_| bases[rng.random_range(0..4)]).collect()
}
#[test]
fn gpu_sa_matches_cpu_small() {
let Some(ctx) = get_ctx() else {
eprintln!("Skipping: GPU not available");
return;
};
let text = encode("ACGTACGTACGTACGT");
let cpu = cpu_sa(&text);
let gpu = pollster::block_on(SaPipelines::new(&ctx).build_suffix_array(&ctx, &text));
assert_eq!(cpu.data, gpu.data, "SA mismatch on small input");
}
#[test]
fn gpu_sa_matches_cpu_1k() {
let Some(ctx) = get_ctx() else {
eprintln!("Skipping: GPU not available");
return;
};
let text = encode(&random_dna(1_000));
let cpu = cpu_sa(&text);
let gpu = pollster::block_on(SaPipelines::new(&ctx).build_suffix_array(&ctx, &text));
assert_eq!(cpu.data, gpu.data, "SA mismatch on 1 K input");
}
#[test]
fn gpu_bwt_matches_cpu_small() {
let Some(ctx) = get_ctx() else {
eprintln!("Skipping: GPU not available");
return;
};
let text = encode("ACGTACGTACGTACGT");
let sa = cpu_sa(&text);
let cpu = cpu_bwt(&text, &sa);
let gpu = pollster::block_on(BwtPipelines::new(&ctx).build_bwt(&ctx, &text, &sa));
assert_eq!(cpu, gpu, "BWT mismatch on small input");
}
#[test]
fn gpu_bwt_matches_cpu_1k() {
let Some(ctx) = get_ctx() else {
eprintln!("Skipping: GPU not available");
return;
};
let text = encode(&random_dna(1_000));
let sa = cpu_sa(&text);
let cpu = cpu_bwt(&text, &sa);
let gpu = pollster::block_on(BwtPipelines::new(&ctx).build_bwt(&ctx, &text, &sa));
assert_eq!(cpu, gpu, "BWT mismatch on 1 K input");
}
fn assert_occ_rank_equal(
cpu: &haystackfm::occ::OccTable,
gpu: &haystackfm::occ::OccTable,
n: u32,
label: &str,
) {
let step = (n / 200).max(1) as usize;
for i in (0..=n).step_by(step) {
for c in 0..ALPHABET_SIZE as u8 {
let cpu_rank = cpu.rank(c, i);
let gpu_rank = gpu.rank(c, i);
assert_eq!(
cpu_rank, gpu_rank,
"{label}: rank mismatch at char={c}, pos={i}"
);
}
}
}
#[test]
fn gpu_occ_matches_cpu_small() {
let Some(ctx) = get_ctx() else {
eprintln!("Skipping: GPU not available");
return;
};
let text = encode("ACGTACGTACGTACGT");
let sa = cpu_sa(&text);
let bwt = cpu_bwt(&text, &sa);
let cpu = cpu_occ(&bwt, OccEncoding::Bitplane);
let gpu = pollster::block_on(OccPipelines::new(&ctx).build_occ_table(&ctx, &bwt));
for i in 0..=bwt.len() as u32 {
for c in 0..ALPHABET_SIZE as u8 {
assert_eq!(
cpu.rank(c, i),
gpu.rank(c, i),
"OCC rank mismatch at char={c}, pos={i}"
);
}
}
}
#[test]
fn gpu_occ_matches_cpu_1k() {
let Some(ctx) = get_ctx() else {
eprintln!("Skipping: GPU not available");
return;
};
let text = encode(&random_dna(1_000));
let sa = cpu_sa(&text);
let bwt = cpu_bwt(&text, &sa);
let n = bwt.len() as u32;
let cpu = cpu_occ(&bwt, OccEncoding::Bitplane);
let gpu = pollster::block_on(OccPipelines::new(&ctx).build_occ_table(&ctx, &bwt));
assert_occ_rank_equal(&cpu, &gpu, n, "1 K OCC");
}