#![allow(unsafe_code)]
#[cfg(target_arch = "x86_64")]
mod avx2;
mod fill;
mod lanes;
#[cfg(target_arch = "aarch64")]
mod neon;
mod profile;
#[cfg(target_arch = "x86_64")]
mod sse41;
use crate::align::sisd::ScalarInit;
use crate::align::{Alignment, AlignmentEngine, AlignmentType, Scoring, SisdEngine};
use crate::graph::Graph;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
struct StripedBuffers<V> {
profile: Vec<V>,
h: Vec<V>,
e: Vec<V>,
f: Vec<V>,
o: Vec<V>,
q: Vec<V>,
masks: Vec<V>,
penalties: Vec<V>,
penalties_c: Vec<V>,
cached_elem_width: usize,
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
impl<V> Default for StripedBuffers<V> {
fn default() -> StripedBuffers<V> {
StripedBuffers {
profile: Vec::new(),
h: Vec::new(),
e: Vec::new(),
f: Vec::new(),
o: Vec::new(),
q: Vec::new(),
masks: Vec::new(),
penalties: Vec::new(),
penalties_c: Vec::new(),
cached_elem_width: 0,
}
}
}
#[derive(Default)]
enum StripedScratch {
#[cfg(target_arch = "x86_64")]
Sse41(StripedBuffers<core::arch::x86_64::__m128i>),
#[cfg(target_arch = "x86_64")]
Avx2(StripedBuffers<core::arch::x86_64::__m256i>),
#[cfg(target_arch = "aarch64")]
NeonI16(StripedBuffers<core::arch::aarch64::int16x8_t>),
#[cfg(target_arch = "aarch64")]
NeonI32(StripedBuffers<core::arch::aarch64::int32x4_t>),
#[default]
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Escalation {
Int16,
Int32,
Fallback,
}
fn escalate(scoring: &Scoring, seq_len: usize, node_count: usize) -> Escalation {
let worst_case = scoring.worst_case_alignment_score(seq_len as i64 + 8, node_count as i64);
if worst_case < i64::from(i32::MIN) + 1024 {
Escalation::Fallback
} else if worst_case < i64::from(i16::MIN) + 1024 {
Escalation::Int32
} else {
Escalation::Int16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Isa {
#[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
Avx2,
#[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
Sse41,
#[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
Neon,
None,
}
#[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
const FORCE_ISA_ENV: &str = "SPOARS_FORCE_ISA";
#[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
fn should_force_sse41(value: Option<&str>) -> bool {
value.is_some_and(|raw| raw.eq_ignore_ascii_case("sse41"))
}
fn detect_isa() -> Isa {
#[cfg(target_arch = "x86_64")]
{
let force_sse41 = should_force_sse41(std::env::var(FORCE_ISA_ENV).ok().as_deref());
if is_x86_feature_detected!("avx2") && !force_sse41 {
return Isa::Avx2;
}
if is_x86_feature_detected!("sse4.1") {
return Isa::Sse41;
}
}
#[cfg(target_arch = "aarch64")]
{
if std::arch::is_aarch64_feature_detected!("neon") {
return Isa::Neon;
}
}
Isa::None
}
pub struct SimdEngine {
#[cfg_attr(
not(any(target_arch = "x86_64", target_arch = "aarch64")),
allow(dead_code)
)]
alignment_type: AlignmentType,
scoring: Scoring,
inner: SisdEngine,
#[cfg_attr(
not(any(target_arch = "x86_64", target_arch = "aarch64")),
allow(dead_code)
)]
scratch: ScalarInit,
#[cfg_attr(
not(any(target_arch = "x86_64", target_arch = "aarch64")),
allow(dead_code)
)]
striped: StripedScratch,
}
impl SimdEngine {
pub fn new(alignment_type: AlignmentType, scoring: Scoring) -> SimdEngine {
SimdEngine {
alignment_type,
scoring,
inner: SisdEngine::new(alignment_type, scoring),
scratch: ScalarInit::default(),
striped: StripedScratch::None,
}
}
#[cfg(target_arch = "x86_64")]
fn sse41_scratch(
&mut self,
) -> (
&mut ScalarInit,
&mut StripedBuffers<core::arch::x86_64::__m128i>,
) {
if !matches!(self.striped, StripedScratch::Sse41(_)) {
self.striped = StripedScratch::Sse41(StripedBuffers::default());
}
let striped = match &mut self.striped {
StripedScratch::Sse41(buffers) => buffers,
_ => unreachable!("just ensured the Sse41 variant"),
};
(&mut self.scratch, striped)
}
#[cfg(target_arch = "x86_64")]
fn avx2_scratch(
&mut self,
) -> (
&mut ScalarInit,
&mut StripedBuffers<core::arch::x86_64::__m256i>,
) {
if !matches!(self.striped, StripedScratch::Avx2(_)) {
self.striped = StripedScratch::Avx2(StripedBuffers::default());
}
let striped = match &mut self.striped {
StripedScratch::Avx2(buffers) => buffers,
_ => unreachable!("just ensured the Avx2 variant"),
};
(&mut self.scratch, striped)
}
#[cfg(target_arch = "aarch64")]
fn neon_i16_scratch(
&mut self,
) -> (
&mut ScalarInit,
&mut StripedBuffers<core::arch::aarch64::int16x8_t>,
) {
if !matches!(self.striped, StripedScratch::NeonI16(_)) {
self.striped = StripedScratch::NeonI16(StripedBuffers::default());
}
let striped = match &mut self.striped {
StripedScratch::NeonI16(buffers) => buffers,
_ => unreachable!("just ensured the NeonI16 variant"),
};
(&mut self.scratch, striped)
}
#[cfg(target_arch = "aarch64")]
fn neon_i32_scratch(
&mut self,
) -> (
&mut ScalarInit,
&mut StripedBuffers<core::arch::aarch64::int32x4_t>,
) {
if !matches!(self.striped, StripedScratch::NeonI32(_)) {
self.striped = StripedScratch::NeonI32(StripedBuffers::default());
}
let striped = match &mut self.striped {
StripedScratch::NeonI32(buffers) => buffers,
_ => unreachable!("just ensured the NeonI32 variant"),
};
(&mut self.scratch, striped)
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[inline(always)]
fn align_simd_linear<S>(
alignment_type: AlignmentType,
scoring: Scoring,
seq: &[u8],
graph: &Graph,
seeded: &mut ScalarInit,
striped: &mut StripedBuffers<S::Vec>,
) -> (Alignment, i32)
where
S: lanes::Simd,
S::Elem: profile::ElemFromI32 + profile::ElemToI32,
{
use crate::align::backtrack::backtrack_linear;
use crate::align::sisd::reseed_scalar_buffers;
use profile::{build_masks, build_penalties, build_profile, destripe_interior, ElemFromI32};
reseed_scalar_buffers(seeded, alignment_type, scoring, seq, graph);
build_profile::<S>(&mut striped.profile, graph, seq, scoring);
let elem_width = core::mem::size_of::<S::Elem>();
if striped.cached_elem_width != elem_width {
striped.masks = build_masks::<S>(S::NEG_INF);
striped.penalties = build_penalties::<S>(S::Elem::from_i32(i32::from(scoring.g)));
striped.cached_elem_width = elem_width;
}
let (max_i, max_j, max_score) = fill::fill_linear::<S>(
graph,
seq.len(),
scoring,
alignment_type,
seeded,
&striped.profile,
&striped.masks,
&striped.penalties,
&mut striped.h,
);
let matrix_width_vecs = seq.len().div_ceil(S::LANES);
destripe_interior::<S>(
&mut seeded.h,
&striped.h[matrix_width_vecs..],
matrix_width_vecs,
seq.len(),
);
let alignment = backtrack_linear(
graph,
&seeded.node_id_to_rank,
&seeded.sequence_profile,
&seeded.h,
seeded.matrix_width,
alignment_type,
&scoring,
max_i,
max_j,
max_score,
);
(alignment, max_score)
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[inline(always)]
fn align_simd_affine<S>(
alignment_type: AlignmentType,
scoring: Scoring,
seq: &[u8],
graph: &Graph,
seeded: &mut ScalarInit,
striped: &mut StripedBuffers<S::Vec>,
) -> (Alignment, i32)
where
S: lanes::Simd,
S::Elem: profile::ElemFromI32 + profile::ElemToI32,
{
use crate::align::backtrack::backtrack_affine;
use crate::align::sisd::reseed_scalar_buffers;
use profile::{build_masks, build_penalties, build_profile, destripe_interior, ElemFromI32};
reseed_scalar_buffers(seeded, alignment_type, scoring, seq, graph);
build_profile::<S>(&mut striped.profile, graph, seq, scoring);
let elem_width = core::mem::size_of::<S::Elem>();
if striped.cached_elem_width != elem_width {
striped.masks = build_masks::<S>(S::NEG_INF);
striped.penalties = build_penalties::<S>(S::Elem::from_i32(i32::from(scoring.e)));
striped.cached_elem_width = elem_width;
}
let (max_i, max_j, max_score) = fill::fill_affine::<S>(
graph,
seq.len(),
scoring,
alignment_type,
seeded,
&striped.profile,
&striped.masks,
&striped.penalties,
&mut striped.h,
&mut striped.e,
&mut striped.f,
);
let matrix_width_vecs = seq.len().div_ceil(S::LANES);
destripe_interior::<S>(
&mut seeded.h,
&striped.h[matrix_width_vecs..],
matrix_width_vecs,
seq.len(),
);
destripe_interior::<S>(
&mut seeded.e,
&striped.e[matrix_width_vecs..],
matrix_width_vecs,
seq.len(),
);
destripe_interior::<S>(
&mut seeded.f,
&striped.f[matrix_width_vecs..],
matrix_width_vecs,
seq.len(),
);
let alignment = backtrack_affine(
graph,
&seeded.node_id_to_rank,
&seeded.sequence_profile,
&seeded.h,
&seeded.e,
&seeded.f,
seeded.matrix_width,
alignment_type,
&scoring,
max_i,
max_j,
max_score,
);
(alignment, max_score)
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[inline(always)]
fn align_simd_convex<S>(
alignment_type: AlignmentType,
scoring: Scoring,
seq: &[u8],
graph: &Graph,
seeded: &mut ScalarInit,
striped: &mut StripedBuffers<S::Vec>,
) -> (Alignment, i32)
where
S: lanes::Simd,
S::Elem: profile::ElemFromI32 + profile::ElemToI32,
{
use crate::align::backtrack::backtrack_convex;
use crate::align::sisd::reseed_scalar_buffers;
use profile::{build_masks, build_penalties, build_profile, destripe_interior, ElemFromI32};
reseed_scalar_buffers(seeded, alignment_type, scoring, seq, graph);
build_profile::<S>(&mut striped.profile, graph, seq, scoring);
let elem_width = core::mem::size_of::<S::Elem>();
if striped.cached_elem_width != elem_width {
striped.masks = build_masks::<S>(S::NEG_INF);
striped.penalties = build_penalties::<S>(S::Elem::from_i32(i32::from(scoring.e)));
striped.penalties_c = build_penalties::<S>(S::Elem::from_i32(i32::from(scoring.c)));
striped.cached_elem_width = elem_width;
}
let (max_i, max_j, max_score) = fill::fill_convex::<S>(
graph,
seq.len(),
scoring,
alignment_type,
seeded,
&striped.profile,
&striped.masks,
&striped.penalties,
&striped.penalties_c,
&mut striped.h,
&mut striped.e,
&mut striped.f,
&mut striped.o,
&mut striped.q,
);
let matrix_width_vecs = seq.len().div_ceil(S::LANES);
for (dst, src) in [
(&mut seeded.h, &striped.h),
(&mut seeded.e, &striped.e),
(&mut seeded.f, &striped.f),
(&mut seeded.o, &striped.o),
(&mut seeded.q, &striped.q),
] {
destripe_interior::<S>(dst, &src[matrix_width_vecs..], matrix_width_vecs, seq.len());
}
let alignment = backtrack_convex(
graph,
&seeded.node_id_to_rank,
&seeded.sequence_profile,
&seeded.h,
&seeded.e,
&seeded.f,
&seeded.o,
&seeded.q,
seeded.matrix_width,
alignment_type,
&scoring,
max_i,
max_j,
max_score,
);
(alignment, max_score)
}
macro_rules! define_simd_runners {
($arch:literal, $feature:literal, $linear:ident, $affine:ident, $convex:ident) => {
#[cfg(target_arch = $arch)]
#[target_feature(enable = $feature)]
unsafe fn $linear<S>(
alignment_type: AlignmentType,
scoring: Scoring,
seq: &[u8],
graph: &Graph,
seeded: &mut ScalarInit,
striped: &mut StripedBuffers<S::Vec>,
) -> (Alignment, i32)
where
S: lanes::Simd,
S::Elem: profile::ElemFromI32 + profile::ElemToI32,
{
align_simd_linear::<S>(alignment_type, scoring, seq, graph, seeded, striped)
}
#[cfg(target_arch = $arch)]
#[target_feature(enable = $feature)]
unsafe fn $affine<S>(
alignment_type: AlignmentType,
scoring: Scoring,
seq: &[u8],
graph: &Graph,
seeded: &mut ScalarInit,
striped: &mut StripedBuffers<S::Vec>,
) -> (Alignment, i32)
where
S: lanes::Simd,
S::Elem: profile::ElemFromI32 + profile::ElemToI32,
{
align_simd_affine::<S>(alignment_type, scoring, seq, graph, seeded, striped)
}
#[cfg(target_arch = $arch)]
#[target_feature(enable = $feature)]
unsafe fn $convex<S>(
alignment_type: AlignmentType,
scoring: Scoring,
seq: &[u8],
graph: &Graph,
seeded: &mut ScalarInit,
striped: &mut StripedBuffers<S::Vec>,
) -> (Alignment, i32)
where
S: lanes::Simd,
S::Elem: profile::ElemFromI32 + profile::ElemToI32,
{
align_simd_convex::<S>(alignment_type, scoring, seq, graph, seeded, striped)
}
};
}
define_simd_runners!(
"x86_64",
"avx2",
run_avx2_linear,
run_avx2_affine,
run_avx2_convex
);
define_simd_runners!(
"x86_64",
"sse4.1",
run_sse41_linear,
run_sse41_affine,
run_sse41_convex
);
define_simd_runners!(
"aarch64",
"neon",
run_neon_linear,
run_neon_affine,
run_neon_convex
);
impl AlignmentEngine for SimdEngine {
fn align(&mut self, seq: &[u8], graph: &Graph) -> (Alignment, i32) {
if graph.nodes.is_empty() || seq.is_empty() {
return (Alignment::new(), 0);
}
let escalation = escalate(&self.scoring, seq.len(), graph.nodes.len());
let isa = detect_isa();
let alignment_type = self.alignment_type;
let scoring = self.scoring;
match escalation {
Escalation::Fallback => self.inner.align(seq, graph),
Escalation::Int32 => match isa {
Isa::Avx2 => {
#[cfg(target_arch = "x86_64")]
{
use crate::align::GapMode;
use avx2::Avx2I32;
let (scratch, striped) = self.avx2_scratch();
match scoring.gap_mode() {
GapMode::Linear => unsafe {
run_avx2_linear::<Avx2I32>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Affine => unsafe {
run_avx2_affine::<Avx2I32>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Convex => unsafe {
run_avx2_convex::<Avx2I32>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
}
}
#[cfg(not(target_arch = "x86_64"))]
{
self.inner.align(seq, graph)
}
}
Isa::Sse41 => {
#[cfg(target_arch = "x86_64")]
{
use crate::align::GapMode;
use sse41::Sse41I32;
let (scratch, striped) = self.sse41_scratch();
match scoring.gap_mode() {
GapMode::Linear => unsafe {
run_sse41_linear::<Sse41I32>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Affine => unsafe {
run_sse41_affine::<Sse41I32>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Convex => unsafe {
run_sse41_convex::<Sse41I32>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
}
}
#[cfg(not(target_arch = "x86_64"))]
{
self.inner.align(seq, graph)
}
}
Isa::Neon => {
#[cfg(target_arch = "aarch64")]
{
use crate::align::GapMode;
use neon::NeonI32;
let (scratch, striped) = self.neon_i32_scratch();
match scoring.gap_mode() {
GapMode::Linear => unsafe {
run_neon_linear::<NeonI32>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Affine => unsafe {
run_neon_affine::<NeonI32>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Convex => unsafe {
run_neon_convex::<NeonI32>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
}
}
#[cfg(not(target_arch = "aarch64"))]
{
self.inner.align(seq, graph)
}
}
Isa::None => self.inner.align(seq, graph),
},
Escalation::Int16 => match isa {
Isa::Avx2 => {
#[cfg(target_arch = "x86_64")]
{
use crate::align::GapMode;
use avx2::Avx2I16;
let (scratch, striped) = self.avx2_scratch();
match scoring.gap_mode() {
GapMode::Linear => unsafe {
run_avx2_linear::<Avx2I16>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Affine => unsafe {
run_avx2_affine::<Avx2I16>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Convex => unsafe {
run_avx2_convex::<Avx2I16>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
}
}
#[cfg(not(target_arch = "x86_64"))]
{
self.inner.align(seq, graph)
}
}
Isa::Sse41 => {
#[cfg(target_arch = "x86_64")]
{
use crate::align::GapMode;
use sse41::Sse41I16;
let (scratch, striped) = self.sse41_scratch();
match scoring.gap_mode() {
GapMode::Linear => unsafe {
run_sse41_linear::<Sse41I16>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Affine => unsafe {
run_sse41_affine::<Sse41I16>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Convex => unsafe {
run_sse41_convex::<Sse41I16>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
}
}
#[cfg(not(target_arch = "x86_64"))]
{
self.inner.align(seq, graph)
}
}
Isa::Neon => {
#[cfg(target_arch = "aarch64")]
{
use crate::align::GapMode;
use neon::NeonI16;
let (scratch, striped) = self.neon_i16_scratch();
match scoring.gap_mode() {
GapMode::Linear => unsafe {
run_neon_linear::<NeonI16>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Affine => unsafe {
run_neon_affine::<NeonI16>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
GapMode::Convex => unsafe {
run_neon_convex::<NeonI16>(
alignment_type,
scoring,
seq,
graph,
scratch,
striped,
)
},
}
}
#[cfg(not(target_arch = "aarch64"))]
{
self.inner.align(seq, graph)
}
}
Isa::None => self.inner.align(seq, graph),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::Graph;
fn linear_graph(seed: &[u8]) -> Graph {
let mut graph = Graph::new();
graph.add_alignment_weight(&[], seed, 1).unwrap();
graph
}
fn assert_matches_sisd(
alignment_type: AlignmentType,
scoring: Scoring,
seq: &[u8],
graph: &Graph,
) {
let mut simd_engine = SimdEngine::new(alignment_type, scoring);
let mut sisd_engine = SisdEngine::new(alignment_type, scoring);
let simd_result = simd_engine.align(seq, graph);
let sisd_result = sisd_engine.align(seq, graph);
assert_eq!(simd_result, sisd_result);
}
#[test]
fn simd_engine_matches_sisd_engine_on_tiny_input() {
let alignment_type = AlignmentType::Global;
let scoring = Scoring::new(5, -4, -8, -6, -10, -4).unwrap();
let seq = b"ACGT";
let graph = Graph::new();
let mut simd_engine = SimdEngine::new(alignment_type, scoring);
let mut sisd_engine = SisdEngine::new(alignment_type, scoring);
let simd_result = simd_engine.align(seq, &graph);
let sisd_result = sisd_engine.align(seq, &graph);
assert_eq!(simd_result, sisd_result);
}
#[test]
fn simd_engine_matches_sisd_engine_with_default_scoring() {
let graph = linear_graph(b"ACGTACGTAC");
let scoring = Scoring::new(5, -4, -8, -6, -10, -4).unwrap();
for alignment_type in [
AlignmentType::Local,
AlignmentType::Global,
AlignmentType::Overlap,
] {
assert_matches_sisd(alignment_type, scoring, b"ACGTTCGTAC", &graph);
}
}
#[test]
fn simd_engine_matches_sisd_engine_with_mid_range_affine_scoring() {
let graph = linear_graph(b"GATTACAGATTACA");
let scoring = Scoring::new(3, -3, -5, -2, -5, -2).unwrap();
assert_matches_sisd(AlignmentType::Local, scoring, b"GATTACAGATTAA", &graph);
}
#[test]
fn simd_engine_matches_sisd_engine_when_escalation_forces_int32_branch() {
let seq_len = 300usize;
let node_count = 300usize;
let scoring = Scoring::new(127, -128, -128, -128, -128, -128).unwrap();
assert_eq!(escalate(&scoring, seq_len, node_count), Escalation::Int32);
let seed = vec![b'A'; node_count];
let graph = linear_graph(&seed);
let seq = vec![b'C'; seq_len];
assert_matches_sisd(AlignmentType::Global, scoring, &seq, &graph);
}
#[test]
fn escalation_predicate_selects_fallback_before_i32_overflow() {
let scoring = Scoring::new(127, -128, -128, -128, -128, -128).unwrap();
let huge_node_count = 20_000_000usize;
let huge_seq_len = 20_000_000usize;
assert_eq!(
escalate(&scoring, huge_seq_len, huge_node_count),
Escalation::Fallback
);
}
#[test]
fn empty_graph_returns_empty_alignment_and_zero_score() {
let alignment_type = AlignmentType::Global;
let scoring = Scoring::new(5, -4, -8, -6, -10, -4).unwrap();
let graph = Graph::new();
let mut engine = SimdEngine::new(alignment_type, scoring);
let (alignment, score) = engine.align(b"ACGT", &graph);
assert_eq!(alignment, Vec::new());
assert_eq!(score, 0);
}
#[test]
fn empty_sequence_returns_empty_alignment_and_zero_score() {
let alignment_type = AlignmentType::Global;
let scoring = Scoring::new(5, -4, -8, -6, -10, -4).unwrap();
let graph = linear_graph(b"ACGT");
let mut engine = SimdEngine::new(alignment_type, scoring);
let (alignment, score) = engine.align(b"", &graph);
assert_eq!(alignment, Vec::new());
assert_eq!(score, 0);
}
#[test]
fn detect_isa_returns_a_defined_variant_and_align_still_matches_sisd() {
let isa = detect_isa();
assert!(matches!(
isa,
Isa::Avx2 | Isa::Sse41 | Isa::Neon | Isa::None
));
let graph = linear_graph(b"ACGTACGTAC");
let scoring = Scoring::new(5, -4, -8, -6, -10, -4).unwrap();
assert_matches_sisd(AlignmentType::Global, scoring, b"ACGTTCGTAC", &graph);
}
#[test]
fn should_force_sse41_only_matches_the_sse41_token() {
assert!(should_force_sse41(Some("sse41")));
assert!(should_force_sse41(Some("SSE41")));
assert!(should_force_sse41(Some("Sse41")));
assert!(!should_force_sse41(None));
assert!(!should_force_sse41(Some("")));
assert!(!should_force_sse41(Some("avx2")));
assert!(!should_force_sse41(Some("sse4.1")));
assert!(!should_force_sse41(Some("neon")));
}
}