mod support;
use proptest::collection::vec;
use proptest::prelude::*;
use spoars::align::{Alignment, AlignmentEngine, AlignmentType, Scoring, SimdEngine, SisdEngine};
use spoars::graph::Graph;
use support::generators::{deterministic_config, small_dna};
fn simd_kernel_active() -> bool {
#[cfg(target_arch = "x86_64")]
{
is_x86_feature_detected!("sse4.1")
}
#[cfg(target_arch = "aarch64")]
{
std::arch::is_aarch64_feature_detected!("neon")
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
false
}
}
fn linear_scoring() -> Scoring {
Scoring::new(5, -4, -8, -8, -8, -8).unwrap()
}
fn affine_scoring() -> Scoring {
Scoring::new(5, -4, -8, -6, -8, -6).unwrap()
}
fn convex_scoring() -> Scoring {
Scoring::new(5, -4, -8, -6, -10, -4).unwrap()
}
fn linear_scoring_large() -> Scoring {
Scoring::new(127, -128, -128, -128, -128, -128).unwrap()
}
fn affine_scoring_large() -> Scoring {
Scoring::new(127, -128, -128, -100, -128, -100).unwrap()
}
fn convex_scoring_large() -> Scoring {
Scoring::new(127, -128, -110, -90, -128, -80).unwrap()
}
const LARGE_MIN_LEN: usize = 190;
const LARGE_MAX_LEN: usize = 230;
fn large_dna(min_len: usize, max_len: usize, n_seqs: usize) -> impl Strategy<Value = Vec<String>> {
let seq = vec(
prop_oneof![Just('A'), Just('C'), Just('G'), Just('T')],
min_len..=max_len,
)
.prop_map(|chars| chars.into_iter().collect::<String>());
vec(seq, n_seqs..=n_seqs)
}
fn assert_forces_int32_escalation(scoring: Scoring, seq_len: usize, node_count: usize) {
let worst_case = scoring.worst_case_alignment_score(seq_len as i64 + 8, node_count as i64);
assert!(
worst_case < i64::from(i16::MIN) + 1024,
"expected the int16->int32 escalation boundary to be crossed: worst_case={worst_case} \
seq_len={seq_len} node_count={node_count}"
);
assert!(
worst_case >= i64::from(i32::MIN) + 1024,
"expected NOT to cross into the fallback tier: worst_case={worst_case} \
seq_len={seq_len} node_count={node_count}"
);
}
#[test]
fn simd_int32_escalation_is_actually_forced_at_the_generators_tightest_margin() {
for (scoring, mode) in [
(linear_scoring_large(), "linear"),
(affine_scoring_large(), "affine"),
(convex_scoring_large(), "convex"),
] {
assert_forces_int32_escalation(scoring, LARGE_MIN_LEN, LARGE_MIN_LEN);
eprintln!("simd_parity: {mode} large-penalty scoring provably forces Escalation::Int32");
}
}
fn drive<E: AlignmentEngine>(
engine: &mut E,
seqs: &[String],
) -> (Vec<Alignment>, Vec<i32>, String) {
let mut graph = Graph::new();
let mut alignments = Vec::with_capacity(seqs.len());
let mut scores = Vec::with_capacity(seqs.len());
for seq in seqs {
let bytes = seq.as_bytes();
let (alignment, score) = engine.align(bytes, &graph);
alignments.push(alignment.clone());
scores.push(score);
graph
.add_alignment_weight(&alignment, bytes, 1)
.expect("add_alignment_weight failed");
}
let consensus = graph.generate_consensus_min_coverage(-1);
(alignments, scores, consensus)
}
#[allow(clippy::type_complexity)]
fn run_both(
alignment_type: AlignmentType,
scoring: Scoring,
seqs: &[String],
) -> (
(Vec<Alignment>, Vec<i32>, String),
(Vec<Alignment>, Vec<i32>, String),
) {
let mut simd = SimdEngine::new(alignment_type, scoring);
let mut sisd = SisdEngine::new(alignment_type, scoring);
(drive(&mut simd, seqs), drive(&mut sisd, seqs))
}
#[test]
fn simd_linear_nw_identical_sequences_align_on_the_diagonal() {
if !simd_kernel_active() {
eprintln!("simd_parity: skipping (no vectorized SIMD ISA active on this target/host)");
return;
}
let seqs = vec!["ACGT".to_string(), "ACGT".to_string()];
let (simd, sisd) = run_both(AlignmentType::Global, linear_scoring(), &seqs);
assert_eq!(simd.0[0], Vec::<(i32, i32)>::new());
assert_eq!(simd.0[1], vec![(0, 0), (1, 1), (2, 2), (3, 3)]);
assert_eq!(simd.2, "ACGT");
assert_eq!(
simd, sisd,
"SIMD must match SISD (alignments, scores, consensus)"
);
}
#[test]
fn simd_linear_nw_one_mismatch_stays_on_the_diagonal() {
if !simd_kernel_active() {
eprintln!("simd_parity: skipping (no vectorized SIMD ISA active on this target/host)");
return;
}
let seqs = vec!["ACGT".to_string(), "ACTT".to_string()];
let (simd, sisd) = run_both(AlignmentType::Global, linear_scoring(), &seqs);
assert_eq!(simd.0[1], vec![(0, 0), (1, 1), (2, 2), (3, 3)]);
assert_eq!(
simd, sisd,
"SIMD must match SISD (alignments, scores, consensus)"
);
}
#[test]
fn simd_linear_nw_multi_segment_single_sequence_added_verbatim() {
if !simd_kernel_active() {
eprintln!("simd_parity: skipping (no vectorized SIMD ISA active on this target/host)");
return;
}
let seqs = vec!["ACGTACGTAC".to_string()];
let (simd, sisd) = run_both(AlignmentType::Global, linear_scoring(), &seqs);
assert_eq!(simd.0, vec![Vec::<(i32, i32)>::new()]);
assert_eq!(simd.2, "ACGTACGTAC");
assert_eq!(
simd, sisd,
"SIMD must match SISD (alignments, scores, consensus)"
);
}
#[test]
fn simd_parity_reports_simd_availability() {
if simd_kernel_active() {
eprintln!("simd_parity: vectorized SIMD ISA active — parity assertions ACTIVE (native)");
} else {
eprintln!("simd_parity: no vectorized SIMD ISA — parity assertions SKIPPED");
}
}
#[test]
fn simd_affine_nw_deletion_run_matches_sisd() {
if !simd_kernel_active() {
eprintln!("simd_parity: skipping (no vectorized SIMD ISA active on this target/host)");
return;
}
let seqs = vec!["ACGTTTTTACGT".to_string(), "ACGTACGT".to_string()];
let (simd, sisd) = run_both(AlignmentType::Global, affine_scoring(), &seqs);
assert_eq!(
simd, sisd,
"SIMD affine NW must match SISD (alignments, scores, consensus)"
);
}
#[test]
fn simd_affine_nw_insertion_run_matches_sisd() {
if !simd_kernel_active() {
eprintln!("simd_parity: skipping (no vectorized SIMD ISA active on this target/host)");
return;
}
let seqs = vec!["ACGTACGT".to_string(), "ACGTTTTTACGT".to_string()];
let (simd, sisd) = run_both(AlignmentType::Global, affine_scoring(), &seqs);
assert_eq!(
simd, sisd,
"SIMD affine NW must match SISD (alignments, scores, consensus)"
);
}
#[test]
fn simd_affine_nw_multi_segment_gap_run_matches_sisd() {
if !simd_kernel_active() {
eprintln!("simd_parity: skipping (no vectorized SIMD ISA active on this target/host)");
return;
}
let seqs = vec![
"ACGTACGTAAAAACGTACGT".to_string(),
"ACGTACGTACGTACGT".to_string(),
];
let (simd, sisd) = run_both(AlignmentType::Global, affine_scoring(), &seqs);
assert_eq!(
simd, sisd,
"SIMD affine NW must match SISD (alignments, scores, consensus)"
);
}
proptest! {
#![proptest_config(ProptestConfig { cases: 48, ..deterministic_config() })]
#[test]
fn simd_affine_matches_sisd(seqs in small_dna(40, 6)) {
if simd_kernel_active() {
for alignment_type in [
AlignmentType::Global,
AlignmentType::Local,
AlignmentType::Overlap,
] {
let (simd, sisd) = run_both(alignment_type, affine_scoring(), &seqs);
prop_assert_eq!(
&simd.0, &sisd.0,
"alignment mismatch type={:?} seqs={:?}", alignment_type, seqs
);
prop_assert_eq!(
&simd.1, &sisd.1,
"score mismatch type={:?} seqs={:?}", alignment_type, seqs
);
prop_assert_eq!(
&simd.2, &sisd.2,
"consensus mismatch type={:?} seqs={:?}", alignment_type, seqs
);
}
}
}
}
#[test]
fn simd_convex_nw_long_deletion_run_uses_second_function() {
if !simd_kernel_active() {
eprintln!("simd_parity: skipping (no vectorized SIMD ISA active on this target/host)");
return;
}
let seqs = vec!["ACGTTTTTTTTACGT".to_string(), "ACGTACGT".to_string()];
let (simd, sisd) = run_both(AlignmentType::Global, convex_scoring(), &seqs);
assert_eq!(
simd, sisd,
"SIMD convex NW must match SISD (alignments, scores, consensus)"
);
}
#[test]
fn simd_convex_nw_long_insertion_run_uses_second_function() {
if !simd_kernel_active() {
eprintln!("simd_parity: skipping (no vectorized SIMD ISA active on this target/host)");
return;
}
let seqs = vec!["ACGTACGT".to_string(), "ACGTTTTTTTTACGT".to_string()];
let (simd, sisd) = run_both(AlignmentType::Global, convex_scoring(), &seqs);
assert_eq!(
simd, sisd,
"SIMD convex NW must match SISD (alignments, scores, consensus)"
);
}
#[test]
fn simd_convex_nw_multi_segment_long_gap_matches_sisd() {
if !simd_kernel_active() {
eprintln!("simd_parity: skipping (no vectorized SIMD ISA active on this target/host)");
return;
}
let seqs = vec![
"ACGTACGTAAAAAAAACGTACGT".to_string(),
"ACGTACGTACGTACGT".to_string(),
];
let (simd, sisd) = run_both(AlignmentType::Global, convex_scoring(), &seqs);
assert_eq!(
simd, sisd,
"SIMD convex NW must match SISD (alignments, scores, consensus)"
);
}
proptest! {
#![proptest_config(ProptestConfig { cases: 48, ..deterministic_config() })]
#[test]
fn simd_convex_matches_sisd(seqs in small_dna(40, 6)) {
if simd_kernel_active() {
for alignment_type in [
AlignmentType::Global,
AlignmentType::Local,
AlignmentType::Overlap,
] {
let (simd, sisd) = run_both(alignment_type, convex_scoring(), &seqs);
prop_assert_eq!(
&simd.0, &sisd.0,
"alignment mismatch type={:?} seqs={:?}", alignment_type, seqs
);
prop_assert_eq!(
&simd.1, &sisd.1,
"score mismatch type={:?} seqs={:?}", alignment_type, seqs
);
prop_assert_eq!(
&simd.2, &sisd.2,
"consensus mismatch type={:?} seqs={:?}", alignment_type, seqs
);
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 48, ..deterministic_config() })]
#[test]
fn simd_linear_matches_sisd(seqs in small_dna(40, 6)) {
if simd_kernel_active() {
for alignment_type in [
AlignmentType::Global,
AlignmentType::Local,
AlignmentType::Overlap,
] {
let (simd, sisd) = run_both(alignment_type, linear_scoring(), &seqs);
prop_assert_eq!(
&simd.0, &sisd.0,
"alignment mismatch type={:?} seqs={:?}", alignment_type, seqs
);
prop_assert_eq!(
&simd.1, &sisd.1,
"score mismatch type={:?} seqs={:?}", alignment_type, seqs
);
prop_assert_eq!(
&simd.2, &sisd.2,
"consensus mismatch type={:?} seqs={:?}", alignment_type, seqs
);
}
}
}
}
type ScoringBuilder = fn() -> Scoring;
proptest! {
#![proptest_config(ProptestConfig { cases: 24, ..deterministic_config() })]
#[test]
fn simd_capstone_all_gap_modes_and_types_match_sisd_int16(seqs in small_dna(40, 6)) {
if simd_kernel_active() {
let builders: [(&str, ScoringBuilder); 3] = [
("linear", linear_scoring),
("affine", affine_scoring),
("convex", convex_scoring),
];
for (mode, build) in builders {
let scoring = build();
for alignment_type in [
AlignmentType::Global,
AlignmentType::Local,
AlignmentType::Overlap,
] {
let (simd, sisd) = run_both(alignment_type, scoring, &seqs);
prop_assert_eq!(
&simd.0, &sisd.0,
"alignment mismatch mode={} type={:?} seqs={:?}", mode, alignment_type, seqs
);
prop_assert_eq!(
&simd.1, &sisd.1,
"score mismatch mode={} type={:?} seqs={:?}", mode, alignment_type, seqs
);
prop_assert_eq!(
&simd.2, &sisd.2,
"consensus mismatch mode={} type={:?} seqs={:?}", mode, alignment_type, seqs
);
}
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 12, ..deterministic_config() })]
#[test]
fn simd_capstone_all_gap_modes_and_types_match_sisd_int32(
seqs in large_dna(LARGE_MIN_LEN, LARGE_MAX_LEN, 2)
) {
if simd_kernel_active() {
let builders: [(&str, ScoringBuilder); 3] = [
("linear", linear_scoring_large),
("affine", affine_scoring_large),
("convex", convex_scoring_large),
];
for (mode, build) in builders {
let scoring = build();
for alignment_type in [
AlignmentType::Global,
AlignmentType::Local,
AlignmentType::Overlap,
] {
let (simd, sisd) = run_both(alignment_type, scoring, &seqs);
prop_assert_eq!(
&simd.0, &sisd.0,
"alignment mismatch mode={} type={:?} seqs={:?}", mode, alignment_type, seqs
);
prop_assert_eq!(
&simd.1, &sisd.1,
"score mismatch mode={} type={:?} seqs={:?}", mode, alignment_type, seqs
);
prop_assert_eq!(
&simd.2, &sisd.2,
"consensus mismatch mode={} type={:?} seqs={:?}", mode, alignment_type, seqs
);
}
}
}
}
}