Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 0 additions & 39 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ flate2 = "1"
rayon = "1"
dashmap = "6"
chrono = "0.4"
rand = "0.10"
tempfile = "3"
bitflags = { version = "2.12.1", features = ["std"] }
shlex = "2.0.1"
Expand Down
32 changes: 30 additions & 2 deletions src/align/read_align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crate::error::Error;
use crate::index::GenomeIndex;
use crate::params::{IntronMotifFilter, IntronStrandFilter, MultimapperOrder, Parameters};
use crate::stats::UnmappedReason;
use rand::{SeedableRng, rngs::StdRng, seq::SliceRandom};
use std::hash::{DefaultHasher, Hash, Hasher};

/// Derive a deterministic per-read RNG seed from `run_rng_seed` + the read name.
Expand All @@ -26,6 +25,35 @@ pub(crate) fn per_read_seed(run_rng_seed: u64, read_name: &str) -> u64 {
run_rng_seed.wrapping_mul(hasher.finish().wrapping_add(1))
}

/// A deterministic `splitmix64` step, ported from STAR-rs (which uses it for its own
/// thread-invariant `--outMultimapperOrder Random` per-read shuffle; see
/// `docs-old/dev/porting-from-star-rs.md`'s RNG decision). Replaces a general-purpose
/// PRNG crate with a small in-tree generator, matching the project's dependency-hygiene
/// preference for self-contained determinism-critical code.
pub(crate) fn splitmix64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}

/// A deterministic Fisher-Yates shuffle of `order` seeded by `seed`, using `splitmix64`.
fn shuffle_deterministic<T>(order: &mut [T], seed: u64) {
let mut s = seed ^ 0xA076_1D64_78BD_642F;
for k in (1..order.len()).rev() {
let j = (splitmix64(&mut s) % (k as u64 + 1)) as usize;
order.swap(k, j);
}
}

/// A single deterministic draw from `0..n`, using the same `splitmix64` mixing as
/// `shuffle_deterministic`.
pub(crate) fn deterministic_index(seed: u64, n: usize) -> usize {
let mut s = seed ^ 0xA076_1D64_78BD_642F;
(splitmix64(&mut s) % n as u64) as usize
}

/// Shuffle the prefix of `items` whose `score_fn` equals the first element's score.
///
/// Mirrors STAR's `ReadAlign_multMapSelect` / `funPrimaryAlignMark`: best-scoring
Expand All @@ -40,7 +68,7 @@ fn shuffle_tied_prefix<T>(items: &mut [T], score_fn: impl Fn(&T) -> i32, seed: u
if tied < 2 {
return;
}
items[..tied].shuffle(&mut StdRng::seed_from_u64(seed));
shuffle_deterministic(&mut items[..tied], seed);
}

/// Result of aligning a single read: (transcripts, chimeric_alignments, n_for_mapq, unmapped_reason)
Expand Down
7 changes: 4 additions & 3 deletions src/index/packed_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,13 @@ mod tests {
// bit), so this pre-existing `PackedArray` limitation is
// never hit in practice. The streaming writer uses `u128`
// and is correct for all widths 1..=64.
use rand::{RngExt, SeedableRng};
let mut rng = rand::rngs::StdRng::seed_from_u64(0x5A_C0DE);
let mut state = 0x5A_C0DEu64;
for &wl in &[1u32, 5, 12, 32, 33, 35, 48, 57] {
for &n in &[0usize, 1, 7, 8, 9, 127, 128, 129, 1000] {
let mask = if wl == 64 { u64::MAX } else { (1u64 << wl) - 1 };
let values: Vec<u64> = (0..n).map(|_| rng.random::<u64>() & mask).collect();
let values: Vec<u64> = (0..n)
.map(|_| crate::align::read_align::splitmix64(&mut state) & mask)
.collect();
assert_matches_packed_array(wl, &values);
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,12 +623,11 @@ fn pick_primary_and_mapq(
read_name: &str,
params: &Parameters,
) -> (usize, u8) {
use crate::align::read_align::per_read_seed;
use crate::align::read_align::{deterministic_index, per_read_seed};
use crate::mapq::calculate_mapq;
use rand::{RngExt, SeedableRng, rngs::StdRng};

let mut rng = StdRng::seed_from_u64(per_read_seed(params.run_rng_seed, read_name));
let primary_hit = rng.random_range(0..n_alignments);
let primary_hit =
deterministic_index(per_read_seed(params.run_rng_seed, read_name), n_alignments);
let mapq = calculate_mapq(n_alignments.max(n_for_mapq), params.out_sam_mapq_unique);
(primary_hit, mapq)
}
Expand Down
Loading