#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BandConfig {
pub base: u32,
pub frac: f32,
}
impl Default for BandConfig {
fn default() -> Self {
BandConfig {
base: 10,
frac: 0.01,
}
}
}
impl BandConfig {
pub fn width(&self, query_len: usize) -> usize {
let frac_cols = (f64::from(self.frac) * query_len as f64).round();
let frac_cols = if frac_cols.is_finite() && frac_cols > 0.0 {
frac_cols as usize } else {
0
};
(self.base as usize).saturating_add(frac_cols)
}
}
pub(crate) fn remaining_path(graph: &crate::graph::Graph, node_id_to_rank: &[u32]) -> Vec<u32> {
let rank_to_node = graph.rank_order();
let mut r = vec![0u32; rank_to_node.len()];
for &node_id in rank_to_node.iter().rev() {
let node = graph.node(node_id);
let rank = node_id_to_rank[node_id.0 as usize] as usize;
let mut best: Option<(i64, usize)> = None; for &edge_id in &node.outedges {
let edge = graph.edge(edge_id);
let succ_rank = node_id_to_rank[edge.head.0 as usize] as usize;
let better = match best {
None => true,
Some((best_weight, best_rank)) => {
edge.weight > best_weight
|| (edge.weight == best_weight && succ_rank < best_rank)
}
};
if better {
best = Some((edge.weight, succ_rank));
}
}
r[rank] = best.map_or(0, |(_, succ_rank)| 1 + r[succ_rank]);
}
r
}
pub(crate) fn anchor(r_len: u32, query_len: usize) -> usize {
query_len.saturating_sub(r_len as usize)
}
pub(crate) fn node_window(
anchor: usize,
mstart: usize,
mend: usize,
w: usize,
query_len: usize,
) -> (usize, usize) {
let lo = mstart.min(anchor);
let hi = mend.max(anchor);
let beg = lo.saturating_sub(w);
let end = hi.saturating_add(w).saturating_add(1).min(query_len);
(beg, end)
}
pub(crate) fn segment_range(
beg: usize,
end: usize,
lanes: usize,
matrix_width_vecs: usize,
) -> (usize, usize) {
let beg_sn = (beg / lanes).min(matrix_width_vecs.saturating_sub(1));
let mut end_sn = end.div_ceil(lanes).min(matrix_width_vecs);
if end_sn <= beg_sn {
end_sn = (beg_sn + 1).min(matrix_width_vecs);
}
(beg_sn, end_sn)
}
pub(crate) struct BandState {
pub(crate) r: Vec<u32>,
pub(crate) best_col: Vec<u32>,
pub(crate) w: usize,
}
impl BandState {
pub(crate) fn new(
graph: &crate::graph::Graph,
node_id_to_rank: &[u32],
query_len: usize,
cfg: BandConfig,
) -> BandState {
let r = remaining_path(graph, node_id_to_rank);
BandState {
best_col: vec![0; r.len()],
r,
w: cfg.width(query_len),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn width_is_base_plus_rounded_fraction() {
let cfg = BandConfig {
base: 10,
frac: 0.01,
};
assert_eq!(cfg.width(0), 10); assert_eq!(cfg.width(235), 12); assert_eq!(cfg.width(1000), 20); }
#[test]
fn width_saturates_and_never_panics() {
let huge = BandConfig {
base: u32::MAX,
frac: f32::MAX,
};
let _ = huge.width(usize::MAX); let neg = BandConfig {
base: 5,
frac: -1.0,
};
assert_eq!(neg.width(100), 5); let nan = BandConfig {
base: 7,
frac: f32::NAN,
};
assert_eq!(nan.width(100), 7); }
#[test]
fn default_is_abpoa() {
assert_eq!(BandConfig::default().base, 10);
assert!((BandConfig::default().frac - 0.01).abs() < 1e-9);
}
use crate::graph::{Graph, NodeId};
fn node_id_to_rank_from(graph: &Graph) -> Vec<u32> {
let mut node_id_to_rank = vec![0u32; graph.num_nodes()];
for (rank, &node_id) in graph.rank_order().iter().enumerate() {
node_id_to_rank[node_id.0 as usize] = rank as u32;
}
node_id_to_rank
}
fn linear_chain_3() -> (Graph, Vec<u32>) {
let mut graph = Graph::new();
graph.add_alignment(&[], b"ACG", &[1, 1, 1]).unwrap();
let node_id_to_rank = node_id_to_rank_from(&graph);
(graph, node_id_to_rank)
}
fn diamond_equal_weights() -> (Graph, Vec<u32>) {
let mut graph = Graph::new();
graph.add_alignment(&[], b"AXZC", &[1, 1, 1, 1]).unwrap();
graph
.add_alignment(&[(0, 0), (-1, 1), (3, 2)], b"AYC", &[1, 1, 1])
.unwrap();
let node_id_to_rank = node_id_to_rank_from(&graph);
(graph, node_id_to_rank)
}
#[test]
fn remaining_path_counts_heaviest_successor_chain() {
let (graph, node_id_to_rank) = linear_chain_3();
let r = remaining_path(&graph, &node_id_to_rank);
assert_eq!(r[2], 0);
assert_eq!(r[1], 1);
assert_eq!(r[0], 2);
}
#[test]
fn anchor_clamps_to_query_bounds() {
assert_eq!(anchor(0, 235), 235); assert_eq!(anchor(2, 235), 233);
assert_eq!(anchor(1000, 235), 0); }
#[test]
fn remaining_path_tie_breaks_by_lowest_rank() {
let (graph, node_id_to_rank) = diamond_equal_weights();
let r = remaining_path(&graph, &node_id_to_rank);
let a_rank = node_id_to_rank[NodeId(0).0 as usize] as usize; let x_rank = node_id_to_rank[NodeId(1).0 as usize] as usize; let y_rank = node_id_to_rank[NodeId(4).0 as usize] as usize; assert!(
x_rank < y_rank,
"fixture invariant: X must rank lower than Y for this to test the tie-break"
);
assert_eq!(
r[a_rank],
1 + r[x_rank],
"A must route through X (lower rank), not Y"
);
assert_ne!(
r[a_rank],
1 + r[y_rank],
"A must not route through Y (higher rank)"
);
let c_rank = node_id_to_rank[NodeId(3).0 as usize] as usize;
let z_rank = node_id_to_rank[NodeId(2).0 as usize] as usize;
assert_eq!(r[c_rank], 0);
assert_eq!(r[y_rank], 1);
assert_eq!(r[z_rank], 1);
assert_eq!(r[x_rank], 2);
assert_eq!(r[a_rank], 3);
}
#[test]
fn node_window_is_union_of_anchor_and_predecessors_widened() {
let (beg, end) = node_window(
100, 90, 110, 12,
235,
);
assert_eq!(beg, 90 - 12);
assert_eq!(end, 110 + 12 + 1);
let (b0, e0) = node_window(5, 5, 5, 12, 235);
assert_eq!(b0, 0);
let _ = e0;
let (_, e_l) = node_window(230, 230, 230, 12, 235);
assert_eq!(e_l, 235);
}
#[test]
fn segment_range_half_open_no_off_by_one() {
let lanes = 8;
let mwv = 240usize.div_ceil(lanes); let (bs, es) = segment_range(0, 240, lanes, mwv);
assert_eq!((bs, es), (0, 30));
let mwv2 = 241usize.div_ceil(lanes); let (_, es2) = segment_range(0, 241, lanes, mwv2);
assert_eq!(es2, 31);
let (bs3, es3) = segment_range(20, 60, 8, 30);
assert_eq!((bs3, es3), (2, 8)); }
#[test]
fn segment_range_never_empty() {
let (bs, es) = segment_range(240, 240, 8, 30);
assert!(es > bs);
assert!(es <= 30);
}
#[test]
fn segment_range_widens_when_end_sn_le_beg_sn() {
assert_eq!(segment_range(232, 232, 8, 30), (29, 30));
}
#[test]
fn band_state_new_builds_r_and_best_col_from_graph() {
let (graph, node_id_to_rank) = linear_chain_3();
let cfg = BandConfig {
base: 10,
frac: 0.01,
};
let state = BandState::new(&graph, &node_id_to_rank, 235, cfg);
assert_eq!(state.r.len(), graph.num_nodes());
assert_eq!(state.best_col.len(), state.r.len());
assert!(state.best_col.iter().all(|&c| c == 0));
assert_eq!(state.w, cfg.width(235));
assert_eq!(state.r, remaining_path(&graph, &node_id_to_rank));
}
}