use super::lanes::Simd;
use crate::align::sisd::{self, ScalarInit};
use crate::align::{AlignmentType, Scoring};
use crate::graph::Graph;
pub(crate) trait ElemFromI32: Copy {
fn from_i32(value: i32) -> Self;
}
impl ElemFromI32 for i16 {
#[inline(always)]
fn from_i32(value: i32) -> i16 {
value as i16
}
}
impl ElemFromI32 for i32 {
#[inline(always)]
fn from_i32(value: i32) -> i32 {
value
}
}
pub(crate) trait ElemToI32: Copy {
fn to_i32(self) -> i32;
}
impl ElemToI32 for i16 {
#[inline(always)]
fn to_i32(self) -> i32 {
i32::from(self)
}
}
impl ElemToI32 for i32 {
#[inline(always)]
fn to_i32(self) -> i32 {
self
}
}
#[inline]
pub(crate) fn build_profile<S>(out: &mut Vec<S::Vec>, graph: &Graph, seq: &[u8], scoring: Scoring)
where
S: Simd,
S::Elem: ElemFromI32,
{
let seq_len = seq.len();
let matrix_width_vecs = seq_len.div_ceil(S::LANES);
let num_codes = graph.num_codes as usize;
let abs = |v: i8| i32::from(v).abs();
let padding_penalty = -(abs(scoring.m)
.max(abs(scoring.n))
.max(abs(scoring.g))
.max(abs(scoring.q)));
out.clear();
out.reserve(num_codes * matrix_width_vecs);
let mut lane_buf = vec![S::Elem::from_i32(0); S::LANES];
for code in 0..num_codes {
let decoded = graph.decoder[code];
for segment in 0..matrix_width_vecs {
for (k, lane) in lane_buf.iter_mut().enumerate() {
let pos = segment * S::LANES + k;
let score = if pos < seq_len {
if decoded == i32::from(seq[pos]) {
i32::from(scoring.m)
} else {
i32::from(scoring.n)
}
} else {
padding_penalty
};
*lane = S::Elem::from_i32(score);
}
out.push(S::loadu(&lane_buf));
}
}
}
#[allow(dead_code)]
#[inline(always)]
pub(crate) fn destripe_interior<S>(
dst: &mut [i32],
matrix: &[S::Vec],
matrix_width_vecs: usize,
seq_len: usize,
) where
S: Simd,
S::Elem: ElemFromI32 + ElemToI32,
{
if matrix_width_vecs == 0 {
return;
}
let row_major_width = seq_len + 1;
let num_interior_rows = matrix.len() / matrix_width_vecs;
let full_segments = seq_len / S::LANES;
let mut lane_buf = vec![S::Elem::from_i32(0); S::LANES];
for row in 0..num_interior_rows {
let i = row + 1;
let row_base = i * row_major_width;
let matrix_row_base = row * matrix_width_vecs;
for segment in 0..full_segments {
let dst_start = row_base + segment * S::LANES + 1;
S::store_widened_i32(
matrix[matrix_row_base + segment],
&mut dst[dst_start..dst_start + S::LANES],
);
}
for segment in full_segments..matrix_width_vecs {
S::storeu(matrix[matrix_row_base + segment], &mut lane_buf);
for (k, &lane) in lane_buf.iter().enumerate() {
let pos = segment * S::LANES + k;
if pos < seq_len {
dst[row_base + pos + 1] = lane.to_i32();
}
}
}
}
}
#[allow(dead_code)]
#[inline]
pub(crate) fn seed_scalar_buffers(
graph: &Graph,
seq: &[u8],
scoring: Scoring,
alignment_type: AlignmentType,
) -> ScalarInit {
sisd::seed_scalar_buffers(alignment_type, scoring, seq, graph)
}
#[inline]
pub(crate) fn build_masks<S>(neg_inf: S::Elem) -> Vec<S::Vec>
where
S: Simd,
S::Elem: ElemFromI32,
{
let zero = S::Elem::from_i32(0);
let mut masks = Vec::with_capacity(S::LOG_LANES as usize + 1);
for j in 0..S::LOG_LANES as usize {
let covered = (1usize << j).min(S::LANES);
let mut lanes = vec![zero; S::LANES];
for lane in lanes.iter_mut().take(covered) {
*lane = neg_inf;
}
masks.push(S::loadu(&lanes));
}
let mut carry = vec![neg_inf; S::LANES];
if let Some(first) = carry.first_mut() {
*first = zero;
}
masks.push(S::loadu(&carry));
masks
}
#[inline]
pub(crate) fn build_penalties<S>(penalty: S::Elem) -> Vec<S::Vec>
where
S: Simd,
{
let mut penalties = Vec::with_capacity(S::LOG_LANES as usize);
if S::LOG_LANES == 0 {
return penalties;
}
let mut current = S::splat(penalty);
penalties.push(current);
for _ in 1..S::LOG_LANES {
current = S::add(current, current);
penalties.push(current);
}
penalties
}
#[cfg(test)]
mod tests {
use super::*;
use crate::align::simd::lanes::{ScalarSimdI16, ScalarSimdI32};
use crate::align::sisd::SisdEngine;
use crate::align::{AlignmentEngine, GapMode};
use crate::graph::Graph;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct TestVec4([i32; 4]);
struct TestSimd4;
impl Simd for TestSimd4 {
type Elem = i32;
type Vec = TestVec4;
const LANES: usize = 4;
const LOG_LANES: u32 = 2;
const LSS: i32 = 4;
const RSS: i32 = 12;
const NEG_INF: i32 = i32::MIN + 1024;
fn splat(value: i32) -> TestVec4 {
TestVec4([value; 4])
}
fn add(a: TestVec4, b: TestVec4) -> TestVec4 {
let mut out = [0i32; 4];
for (o, (x, y)) in out.iter_mut().zip(a.0.iter().zip(b.0.iter())) {
*o = x.wrapping_add(*y);
}
TestVec4(out)
}
fn sub(a: TestVec4, b: TestVec4) -> TestVec4 {
let mut out = [0i32; 4];
for (o, (x, y)) in out.iter_mut().zip(a.0.iter().zip(b.0.iter())) {
*o = x.wrapping_sub(*y);
}
TestVec4(out)
}
fn adds(a: TestVec4, b: TestVec4) -> TestVec4 {
let mut out = [0i32; 4];
for (o, (x, y)) in out.iter_mut().zip(a.0.iter().zip(b.0.iter())) {
*o = x.saturating_add(*y);
}
TestVec4(out)
}
fn subs(a: TestVec4, b: TestVec4) -> TestVec4 {
let mut out = [0i32; 4];
for (o, (x, y)) in out.iter_mut().zip(a.0.iter().zip(b.0.iter())) {
*o = x.saturating_sub(*y);
}
TestVec4(out)
}
fn min(a: TestVec4, b: TestVec4) -> TestVec4 {
let mut out = [0i32; 4];
for (o, (x, y)) in out.iter_mut().zip(a.0.iter().zip(b.0.iter())) {
*o = (*x).min(*y);
}
TestVec4(out)
}
fn max(a: TestVec4, b: TestVec4) -> TestVec4 {
let mut out = [0i32; 4];
for (o, (x, y)) in out.iter_mut().zip(a.0.iter().zip(b.0.iter())) {
*o = (*x).max(*y);
}
TestVec4(out)
}
fn or(a: TestVec4, b: TestVec4) -> TestVec4 {
let mut out = [0i32; 4];
for (o, (x, y)) in out.iter_mut().zip(a.0.iter().zip(b.0.iter())) {
*o = x | y;
}
TestVec4(out)
}
fn loadu(src: &[i32]) -> TestVec4 {
TestVec4([src[0], src[1], src[2], src[3]])
}
fn storeu(v: TestVec4, dst: &mut [i32]) {
dst[..4].copy_from_slice(&v.0);
}
fn store_widened_i32(v: TestVec4, dst: &mut [i32]) {
dst[..4].copy_from_slice(&v.0);
}
fn slli<const N: i32>(v: TestVec4) -> TestVec4 {
let lane_shift = (N / 4) as usize;
let mut out = [0i32; 4];
out[lane_shift..4].copy_from_slice(&v.0[..(4 - lane_shift)]);
TestVec4(out)
}
fn srli<const N: i32>(v: TestVec4) -> TestVec4 {
let lane_shift = (N / 4) as usize;
let mut out = [0i32; 4];
out[..(4 - lane_shift)].copy_from_slice(&v.0[lane_shift..4]);
TestVec4(out)
}
fn slli_one_lane(v: TestVec4) -> TestVec4 {
Self::slli::<4>(v)
}
fn srli_top_lane(v: TestVec4) -> TestVec4 {
Self::srli::<12>(v)
}
fn horizontal_max(v: TestVec4) -> i32 {
v.0.iter().fold(0, |acc, &x| acc.max(x))
}
fn prefix_max(v: TestVec4, penalties: &[TestVec4], masks: &[TestVec4]) -> TestVec4 {
let mut a = v;
a = Self::max(
a,
Self::or(masks[0], Self::slli::<4>(Self::add(a, penalties[0]))),
);
a = Self::max(
a,
Self::or(masks[1], Self::slli::<8>(Self::add(a, penalties[1]))),
);
a
}
}
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_graph(seed: &[u8]) -> Graph {
let mut graph = Graph::new();
graph.add_alignment_weight(&[], seed, 1).unwrap();
graph
}
#[test]
fn build_profile_scalar_matches_sisd_sequence_profile() {
let graph = linear_graph(b"ACGT");
let scoring = linear_scoring();
let seq = b"AGGT";
let mut profile = Vec::new();
build_profile::<ScalarSimdI32>(&mut profile, &graph, seq, scoring);
let seeded = seed_scalar_buffers(&graph, seq, scoring, AlignmentType::Global);
let matrix_width_vecs = seq.len(); for code in 0..graph.num_codes as usize {
for j in 0..seq.len() {
let striped = profile[code * matrix_width_vecs + j];
let row_major = seeded.sequence_profile[code * seeded.matrix_width + (j + 1)];
assert_eq!(striped, row_major, "code={code} pos={j}");
}
}
}
#[test]
fn build_profile_multi_lane_pads_trailing_lanes_past_seq_len() {
let graph = linear_graph(b"A"); let scoring = Scoring::new(5, -4, -8, -6, -10, -4).unwrap(); let seq = b"AAAAA";
let mut profile = Vec::new();
build_profile::<TestSimd4>(&mut profile, &graph, seq, scoring);
assert_eq!(profile.len(), 2);
assert_eq!(profile[0], TestVec4([5, 5, 5, 5]));
assert_eq!(profile[1], TestVec4([5, -10, -10, -10]));
}
#[test]
fn build_profile_multi_lane_scores_mismatches() {
let graph = linear_graph(b"A");
let scoring = linear_scoring();
let seq = b"CCCC";
let mut profile = Vec::new();
build_profile::<TestSimd4>(&mut profile, &graph, seq, scoring);
assert_eq!(profile.len(), 1);
assert_eq!(profile[0], TestVec4([-4, -4, -4, -4]));
}
#[test]
fn destripe_interior_scalar_places_cells_at_row_major_offsets() {
let seq_len = 3;
let mw = seq_len + 1; let num_interior_rows = 2;
let mut dst = vec![-1i32; (num_interior_rows + 1) * mw];
let matrix: Vec<i32> = vec![10, 11, 12, 20, 21, 22];
destripe_interior::<ScalarSimdI32>(&mut dst, &matrix, seq_len, seq_len);
assert_eq!(dst[mw + 1], 10);
assert_eq!(dst[mw + 2], 11);
assert_eq!(dst[mw + 3], 12);
assert_eq!(dst[2 * mw + 1], 20);
assert_eq!(dst[2 * mw + 2], 21);
assert_eq!(dst[2 * mw + 3], 22);
assert_eq!(dst[0], -1);
assert_eq!(dst[1], -1);
assert_eq!(dst[mw], -1);
assert_eq!(dst[2 * mw], -1);
}
#[test]
fn destripe_interior_multi_lane_skips_padding_lanes() {
let seq_len = 5;
let mw = seq_len + 1; let mut dst = vec![-1i32; 2 * mw];
let matrix = vec![
TestVec4([100, 101, 102, 103]), TestVec4([104, 999, 999, 999]), ];
destripe_interior::<TestSimd4>(&mut dst, &matrix, 2, seq_len);
assert_eq!(dst[mw + 1], 100);
assert_eq!(dst[mw + 2], 101);
assert_eq!(dst[mw + 3], 102);
assert_eq!(dst[mw + 4], 103);
assert_eq!(dst[mw + 5], 104);
assert_eq!(dst[0], -1);
assert_eq!(dst[mw], -1);
}
#[test]
fn seed_scalar_buffers_matches_sisd_initialize_for_nw_linear() {
let graph = linear_graph(b"AC");
let scoring = linear_scoring();
let seq = b"AG";
let seeded = seed_scalar_buffers(&graph, seq, scoring, AlignmentType::Global);
assert_eq!(scoring.gap_mode(), GapMode::Linear);
assert_eq!(seeded.matrix_width, 3);
assert_eq!(seeded.h[0], 0);
assert_eq!(seeded.h[1], -8);
assert_eq!(seeded.h[2], -16);
assert_eq!(seeded.h[seeded.matrix_width], -8);
assert!(seeded.e.is_empty());
assert!(seeded.f.is_empty());
assert!(seeded.o.is_empty());
assert!(seeded.q.is_empty());
}
#[test]
fn seed_scalar_buffers_matches_sisd_initialize_for_sw_affine() {
let graph = linear_graph(b"AC");
let scoring = affine_scoring();
let seq = b"AG";
let seeded = seed_scalar_buffers(&graph, seq, scoring, AlignmentType::Local);
assert_eq!(scoring.gap_mode(), GapMode::Affine);
assert!(seeded.h[..3].iter().all(|&v| v == 0));
assert_eq!(seeded.f[0], 0);
assert_eq!(seeded.e[0], 0);
assert!(seeded.o.is_empty());
assert!(seeded.q.is_empty());
}
#[test]
fn seed_scalar_buffers_matches_sisd_initialize_for_ov_convex() {
let graph = linear_graph(b"AC");
let scoring = convex_scoring();
let seq = b"AG";
let seeded = seed_scalar_buffers(&graph, seq, scoring, AlignmentType::Overlap);
assert_eq!(scoring.gap_mode(), GapMode::Convex);
assert_eq!(seeded.o[0], 0);
assert_eq!(seeded.q[0], 0);
assert_eq!(seeded.f[0], 0);
assert_eq!(seeded.e[0], 0);
assert_eq!(seeded.h[seeded.matrix_width], 0);
}
#[test]
fn seed_scalar_buffers_node_id_to_rank_matches_graph_topological_order() {
let mut graph = Graph::new();
graph.add_alignment_weight(&[], b"ACGT", 1).unwrap();
graph.add_alignment_weight(&[], b"ACTT", 1).unwrap();
let scoring = linear_scoring();
let seq = b"ACGT";
let seeded = seed_scalar_buffers(&graph, seq, scoring, AlignmentType::Global);
assert_eq!(seeded.node_id_to_rank.len(), graph.nodes.len());
for (rank, &node_id) in graph.rank_to_node.iter().enumerate() {
assert_eq!(seeded.node_id_to_rank[node_id.0 as usize], rank as u32);
}
let mut engine = SisdEngine::new(AlignmentType::Global, scoring);
let (_alignment, score) = engine.align(seq, &graph);
assert!(score > i32::MIN / 2);
}
#[test]
fn build_masks_scalar_single_lane_carry_mask_only() {
let masks = build_masks::<ScalarSimdI16>(ScalarSimdI16::NEG_INF);
assert_eq!(masks.len(), 1);
assert_eq!(masks[0], 0);
}
#[test]
fn build_masks_multi_lane_has_neg_inf_in_expected_low_lanes() {
let neg_inf = TestSimd4::NEG_INF;
let masks = build_masks::<TestSimd4>(neg_inf);
assert_eq!(masks.len(), 3);
assert_eq!(masks[0], TestVec4([neg_inf, 0, 0, 0]));
assert_eq!(masks[1], TestVec4([neg_inf, neg_inf, 0, 0]));
assert_eq!(masks[2], TestVec4([0, neg_inf, neg_inf, neg_inf]));
}
#[test]
fn build_penalties_scalar_single_lane_is_empty() {
let penalties = build_penalties::<ScalarSimdI16>(-8);
assert!(penalties.is_empty()); }
#[test]
fn build_penalties_multi_lane_scales_by_power_of_two() {
let penalties = build_penalties::<TestSimd4>(-8);
assert_eq!(penalties.len(), 2); assert_eq!(penalties[0], TestVec4([-8, -8, -8, -8])); assert_eq!(penalties[1], TestVec4([-16, -16, -16, -16])); }
}