use crate::error::{DnaError, Result};
use crate::types::{DnaSequence, Nucleotide, QualityScore};
use serde::{Deserialize, Serialize};
use std::io::{Read, Write};
pub const MAGIC: [u8; 8] = *b"RVDNA\x01\x00\x00";
pub const FORMAT_VERSION: u16 = 1;
pub const NUM_SECTIONS: usize = 7;
pub const SECTION_ALIGN: u64 = 64;
pub const HEADER_SIZE: u64 = 64 + (NUM_SECTIONS as u64 * 16);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum Codec {
None = 0,
Lz4 = 1,
Zstd = 2,
}
impl Codec {
fn from_u8(v: u8) -> Result<Self> {
match v {
0 => Ok(Codec::None),
1 => Ok(Codec::Lz4),
2 => Ok(Codec::Zstd),
_ => Err(DnaError::InvalidSequence(format!("Unknown codec: {}", v))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum SectionType {
Sequence = 0,
KmerVectors = 1,
AttentionWeights = 2,
VariantTensor = 3,
ProteinEmbeddings = 4,
EpigenomicTracks = 5,
Metadata = 6,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct SectionEntry {
pub offset: u64,
pub size: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RvdnaHeader {
pub version: u16,
pub codec: Codec,
pub flags: u32,
pub sequence_length: u64,
pub num_contigs: u32,
pub sections: [SectionEntry; NUM_SECTIONS],
pub header_checksum: u32,
}
impl RvdnaHeader {
pub fn new(sequence_length: u64, codec: Codec) -> Self {
Self {
version: FORMAT_VERSION,
codec,
flags: 0x01, sequence_length,
num_contigs: 1,
sections: [SectionEntry { offset: 0, size: 0 }; NUM_SECTIONS],
header_checksum: 0,
}
}
pub fn with_quality(mut self) -> Self {
self.flags |= 0x02;
self
}
pub fn has_quality(&self) -> bool {
self.flags & 0x02 != 0
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(HEADER_SIZE as usize);
buf.extend_from_slice(&MAGIC);
buf.extend_from_slice(&self.version.to_le_bytes());
buf.push(self.codec as u8);
buf.push(0);
buf.extend_from_slice(&self.flags.to_le_bytes());
buf.extend_from_slice(&self.sequence_length.to_le_bytes());
buf.extend_from_slice(&self.num_contigs.to_le_bytes());
buf.extend_from_slice(&[0u8; 36]);
for section in &self.sections {
buf.extend_from_slice(§ion.offset.to_le_bytes());
buf.extend_from_slice(§ion.size.to_le_bytes());
}
let checksum = crc32_simple(&buf);
buf.extend_from_slice(&checksum.to_le_bytes());
buf
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < HEADER_SIZE as usize + 4 {
return Err(DnaError::InvalidSequence("Header too short".to_string()));
}
if &data[0..8] != &MAGIC {
return Err(DnaError::InvalidSequence(
"Invalid RVDNA magic number".to_string(),
));
}
let version = u16::from_le_bytes([data[8], data[9]]);
let codec = Codec::from_u8(data[10])?;
let flags = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
let sequence_length = u64::from_le_bytes(data[16..24].try_into().unwrap());
let num_contigs = u32::from_le_bytes(data[24..28].try_into().unwrap());
let mut sections = [SectionEntry { offset: 0, size: 0 }; NUM_SECTIONS];
let table_start = 64;
for i in 0..NUM_SECTIONS {
let base = table_start + i * 16;
sections[i] = SectionEntry {
offset: u64::from_le_bytes(data[base..base + 8].try_into().unwrap()),
size: u64::from_le_bytes(data[base + 8..base + 16].try_into().unwrap()),
};
}
let checksum_offset = table_start + NUM_SECTIONS * 16;
let header_checksum = u32::from_le_bytes(
data[checksum_offset..checksum_offset + 4]
.try_into()
.unwrap(),
);
let computed = crc32_simple(&data[..checksum_offset]);
if computed != header_checksum {
return Err(DnaError::InvalidSequence(format!(
"Header checksum mismatch: expected {:08x}, got {:08x}",
header_checksum, computed
)));
}
Ok(Self {
version,
codec,
flags,
sequence_length,
num_contigs,
sections,
header_checksum,
})
}
}
pub fn encode_2bit(sequence: &[Nucleotide]) -> (Vec<u8>, Vec<u8>) {
let num_bytes = (sequence.len() + 3) / 4;
let mut packed = vec![0u8; num_bytes];
let mask_bytes = (sequence.len() + 7) / 8;
let mut n_mask = vec![0u8; mask_bytes];
for (i, &base) in sequence.iter().enumerate() {
let byte_idx = i / 4;
let bit_offset = 6 - (i % 4) * 2;
let bits = match base {
Nucleotide::A => 0b00,
Nucleotide::C => 0b01,
Nucleotide::G => 0b10,
Nucleotide::T => 0b11,
Nucleotide::N => {
n_mask[i / 8] |= 1 << (7 - i % 8);
0b00 }
};
packed[byte_idx] |= bits << bit_offset;
}
(packed, n_mask)
}
pub fn decode_2bit(packed: &[u8], n_mask: &[u8], length: usize) -> Vec<Nucleotide> {
let mut sequence = Vec::with_capacity(length);
for i in 0..length {
let byte_idx = i / 4;
let bit_offset = 6 - (i % 4) * 2;
let bits = (packed[byte_idx] >> bit_offset) & 0b11;
let is_n = if i / 8 < n_mask.len() {
(n_mask[i / 8] >> (7 - i % 8)) & 1 == 1
} else {
false
};
let base = if is_n {
Nucleotide::N
} else {
match bits {
0b00 => Nucleotide::A,
0b01 => Nucleotide::C,
0b10 => Nucleotide::G,
0b11 => Nucleotide::T,
_ => unreachable!(),
}
};
sequence.push(base);
}
sequence
}
pub fn encode_quality(qualities: &[u8]) -> Vec<u8> {
let mut encoded = Vec::with_capacity((qualities.len() * 6 + 7) / 8);
let mut bit_buffer: u64 = 0;
let mut bits_in_buffer = 0;
for &q in qualities {
let q6 = q.min(63) as u64; bit_buffer = (bit_buffer << 6) | q6;
bits_in_buffer += 6;
while bits_in_buffer >= 8 {
bits_in_buffer -= 8;
encoded.push((bit_buffer >> bits_in_buffer) as u8);
bit_buffer &= (1 << bits_in_buffer) - 1;
}
}
if bits_in_buffer > 0 {
encoded.push((bit_buffer << (8 - bits_in_buffer)) as u8);
}
encoded
}
pub fn decode_quality(encoded: &[u8], count: usize) -> Vec<u8> {
let mut qualities = Vec::with_capacity(count);
let mut bit_buffer: u64 = 0;
let mut bits_in_buffer = 0;
let mut byte_idx = 0;
for _ in 0..count {
while bits_in_buffer < 6 && byte_idx < encoded.len() {
bit_buffer = (bit_buffer << 8) | encoded[byte_idx] as u64;
bits_in_buffer += 8;
byte_idx += 1;
}
bits_in_buffer -= 6;
let q = ((bit_buffer >> bits_in_buffer) & 0x3F) as u8;
bit_buffer &= (1 << bits_in_buffer) - 1;
qualities.push(q);
}
qualities
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SparseAttention {
pub rows: Vec<u32>,
pub cols: Vec<u32>,
pub values: Vec<f32>,
pub shape: (u32, u32),
pub window_size: u32,
}
impl SparseAttention {
pub fn from_dense(matrix: &[f32], rows: usize, cols: usize, threshold: f32) -> Self {
let mut row_idx = Vec::new();
let mut col_idx = Vec::new();
let mut values = Vec::new();
for i in 0..rows {
for j in 0..cols {
let val = matrix[i * cols + j];
if val.abs() > threshold {
row_idx.push(i as u32);
col_idx.push(j as u32);
values.push(val);
}
}
}
Self {
rows: row_idx,
cols: col_idx,
values,
shape: (rows as u32, cols as u32),
window_size: cols as u32,
}
}
pub fn nnz(&self) -> usize {
self.values.len()
}
pub fn sparsity(&self) -> f64 {
let total = self.shape.0 as f64 * self.shape.1 as f64;
if total == 0.0 {
return 1.0;
}
1.0 - (self.nnz() as f64 / total)
}
pub fn get(&self, row: u32, col: u32) -> f32 {
for i in 0..self.values.len() {
if self.rows[i] == row && self.cols[i] == col {
return self.values[i];
}
}
0.0
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&self.shape.0.to_le_bytes());
buf.extend_from_slice(&self.shape.1.to_le_bytes());
buf.extend_from_slice(&self.window_size.to_le_bytes());
let nnz = self.nnz() as u32;
buf.extend_from_slice(&nnz.to_le_bytes());
for &r in &self.rows {
buf.extend_from_slice(&r.to_le_bytes());
}
for &c in &self.cols {
buf.extend_from_slice(&c.to_le_bytes());
}
for &v in &self.values {
buf.extend_from_slice(&v.to_le_bytes());
}
buf
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < 20 {
return Err(DnaError::InvalidSequence(
"Attention data too short".to_string(),
));
}
let shape_0 = u32::from_le_bytes(data[0..4].try_into().unwrap());
let shape_1 = u32::from_le_bytes(data[4..8].try_into().unwrap());
let window_size = u32::from_le_bytes(data[8..12].try_into().unwrap());
let nnz = u32::from_le_bytes(data[12..16].try_into().unwrap()) as usize;
let expected = 16 + nnz * 12; if data.len() < expected {
return Err(DnaError::InvalidSequence(
"Attention data truncated".to_string(),
));
}
let mut offset = 16;
let rows: Vec<u32> = (0..nnz)
.map(|_| {
let v = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
offset += 4;
v
})
.collect();
let cols: Vec<u32> = (0..nnz)
.map(|_| {
let v = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
offset += 4;
v
})
.collect();
let values: Vec<f32> = (0..nnz)
.map(|_| {
let v = f32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
offset += 4;
v
})
.collect();
Ok(Self {
rows,
cols,
values,
shape: (shape_0, shape_1),
window_size,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariantTensor {
pub positions: Vec<u64>,
pub ref_alleles: Vec<u8>,
pub alt_alleles: Vec<u8>,
pub likelihoods: Vec<[u16; 3]>,
pub qualities: Vec<u8>,
}
impl VariantTensor {
pub fn new() -> Self {
Self {
positions: Vec::new(),
ref_alleles: Vec::new(),
alt_alleles: Vec::new(),
likelihoods: Vec::new(),
qualities: Vec::new(),
}
}
pub fn add_variant(
&mut self,
position: u64,
ref_allele: Nucleotide,
alt_allele: Nucleotide,
gl_hom_ref: f32,
gl_het: f32,
gl_hom_alt: f32,
quality: u8,
) {
self.positions.push(position);
self.ref_alleles.push(nucleotide_to_2bit(ref_allele));
self.alt_alleles.push(nucleotide_to_2bit(alt_allele));
self.likelihoods.push([
f32_to_f16(gl_hom_ref),
f32_to_f16(gl_het),
f32_to_f16(gl_hom_alt),
]);
self.qualities.push(quality);
}
pub fn len(&self) -> usize {
self.positions.len()
}
pub fn is_empty(&self) -> bool {
self.positions.is_empty()
}
pub fn get_likelihoods(&self, position: u64) -> Option<[f32; 3]> {
self.positions.binary_search(&position).ok().map(|idx| {
[
f16_to_f32(self.likelihoods[idx][0]),
f16_to_f32(self.likelihoods[idx][1]),
f16_to_f32(self.likelihoods[idx][2]),
]
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
let count = self.len() as u32;
buf.extend_from_slice(&count.to_le_bytes());
for &pos in &self.positions {
buf.extend_from_slice(&pos.to_le_bytes());
}
buf.extend_from_slice(&self.ref_alleles);
buf.extend_from_slice(&self.alt_alleles);
for &gl in &self.likelihoods {
for &v in &gl {
buf.extend_from_slice(&v.to_le_bytes());
}
}
buf.extend_from_slice(&self.qualities);
buf
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < 4 {
return Err(DnaError::InvalidSequence(
"Variant tensor too short".to_string(),
));
}
let count = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
let mut offset = 4;
let positions: Vec<u64> = (0..count)
.map(|_| {
let v = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
offset += 8;
v
})
.collect();
let ref_alleles = data[offset..offset + count].to_vec();
offset += count;
let alt_alleles = data[offset..offset + count].to_vec();
offset += count;
let likelihoods: Vec<[u16; 3]> = (0..count)
.map(|_| {
let a = u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap());
offset += 2;
let b = u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap());
offset += 2;
let c = u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap());
offset += 2;
[a, b, c]
})
.collect();
let qualities = data[offset..offset + count].to_vec();
Ok(Self {
positions,
ref_alleles,
alt_alleles,
likelihoods,
qualities,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KmerVectorBlock {
pub k: u32,
pub dimensions: u32,
pub start_pos: u64,
pub region_len: u64,
pub vector: Vec<f32>,
pub quantized: Option<Vec<i8>>,
pub quant_scale: f32,
}
impl KmerVectorBlock {
pub fn from_sequence(
sequence: &DnaSequence,
start: u64,
len: u64,
k: u32,
dimensions: u32,
) -> Result<Self> {
let end = (start + len).min(sequence.len() as u64);
let subseq_bases: Vec<Nucleotide> = (start as usize..end as usize)
.map(|i| sequence.get(i).unwrap_or(Nucleotide::N))
.collect();
let subseq = DnaSequence::new(subseq_bases);
let vector = subseq.to_kmer_vector(k as usize, dimensions as usize)?;
let max_abs = vector.iter().map(|v| v.abs()).fold(0.0f32, f32::max);
let scale = if max_abs > 0.0 { max_abs / 127.0 } else { 1.0 };
let quantized: Vec<i8> = vector
.iter()
.map(|&v| (v / scale).round().max(-128.0).min(127.0) as i8)
.collect();
Ok(Self {
k,
dimensions,
start_pos: start,
region_len: end - start,
vector,
quantized: Some(quantized),
quant_scale: scale,
})
}
pub fn cosine_similarity(&self, other: &[f32]) -> f32 {
if self.vector.len() != other.len() {
return 0.0;
}
let dot: f32 = self.vector.iter().zip(other).map(|(a, b)| a * b).sum();
let mag_a: f32 = self.vector.iter().map(|a| a * a).sum::<f32>().sqrt();
let mag_b: f32 = other.iter().map(|b| b * b).sum::<f32>().sqrt();
if mag_a == 0.0 || mag_b == 0.0 {
0.0
} else {
dot / (mag_a * mag_b)
}
}
pub fn fast_similarity(&self, other_quantized: &[i8]) -> f32 {
match &self.quantized {
Some(q) if q.len() == other_quantized.len() => {
let dot: i32 = q
.iter()
.zip(other_quantized)
.map(|(&a, &b)| a as i32 * b as i32)
.sum();
dot as f32 * self.quant_scale * self.quant_scale
}
_ => 0.0,
}
}
}
pub struct RvdnaWriter {
header: RvdnaHeader,
sequence_data: Option<(Vec<u8>, Vec<u8>)>, quality_data: Option<Vec<u8>>,
kmer_blocks: Vec<KmerVectorBlock>,
attention: Option<SparseAttention>,
variants: Option<VariantTensor>,
metadata: Option<serde_json::Value>,
}
impl RvdnaWriter {
pub fn new(sequence: &DnaSequence, codec: Codec) -> Self {
let (packed, n_mask) = encode_2bit(sequence.bases());
Self {
header: RvdnaHeader::new(sequence.len() as u64, codec),
sequence_data: Some((packed, n_mask)),
quality_data: None,
kmer_blocks: Vec::new(),
attention: None,
variants: None,
metadata: None,
}
}
pub fn with_quality(mut self, qualities: &[u8]) -> Self {
self.quality_data = Some(encode_quality(qualities));
self.header = self.header.with_quality();
self
}
pub fn with_kmer_vectors(
mut self,
sequence: &DnaSequence,
k: u32,
dimensions: u32,
block_size: u64,
) -> Result<Self> {
let seq_len = sequence.len() as u64;
let mut pos = 0u64;
while pos < seq_len {
let len = block_size.min(seq_len - pos);
if len >= k as u64 {
let block = KmerVectorBlock::from_sequence(sequence, pos, len, k, dimensions)?;
self.kmer_blocks.push(block);
}
pos += block_size;
}
Ok(self)
}
pub fn with_attention(mut self, attention: SparseAttention) -> Self {
self.attention = Some(attention);
self
}
pub fn with_variants(mut self, variants: VariantTensor) -> Self {
self.variants = Some(variants);
self
}
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
pub fn write<W: Write>(&mut self, writer: &mut W) -> Result<usize> {
let mut sections_data: Vec<Vec<u8>> = vec![Vec::new(); NUM_SECTIONS];
if let Some((ref packed, ref n_mask)) = self.sequence_data {
let mut sec = Vec::new();
sec.extend_from_slice(&(packed.len() as u32).to_le_bytes());
sec.extend_from_slice(&(n_mask.len() as u32).to_le_bytes());
sec.extend_from_slice(packed);
sec.extend_from_slice(n_mask);
if let Some(ref qual) = self.quality_data {
sec.extend_from_slice(&(qual.len() as u32).to_le_bytes());
sec.extend_from_slice(qual);
}
sections_data[0] = sec;
}
if !self.kmer_blocks.is_empty() {
let mut sec = Vec::new();
sec.extend_from_slice(&(self.kmer_blocks.len() as u32).to_le_bytes());
for block in &self.kmer_blocks {
let block_bytes = serde_json::to_vec(block)
.map_err(|e| DnaError::PipelineError(e.to_string()))?;
sec.extend_from_slice(&(block_bytes.len() as u32).to_le_bytes());
sec.extend_from_slice(&block_bytes);
}
sections_data[1] = sec;
}
if let Some(ref attn) = self.attention {
sections_data[2] = attn.to_bytes();
}
if let Some(ref variants) = self.variants {
sections_data[3] = variants.to_bytes();
}
if let Some(ref meta) = self.metadata {
let meta_bytes =
serde_json::to_vec(meta).map_err(|e| DnaError::PipelineError(e.to_string()))?;
sections_data[6] = meta_bytes;
}
let header_len = HEADER_SIZE + 4; let mut current_offset = align_up(header_len, SECTION_ALIGN);
for i in 0..NUM_SECTIONS {
if !sections_data[i].is_empty() {
self.header.sections[i] = SectionEntry {
offset: current_offset,
size: sections_data[i].len() as u64,
};
current_offset = align_up(
current_offset + sections_data[i].len() as u64,
SECTION_ALIGN,
);
}
}
let header_bytes = self.header.to_bytes();
writer.write_all(&header_bytes).map_err(DnaError::IoError)?;
let pad_len =
align_up(header_bytes.len() as u64, SECTION_ALIGN) - header_bytes.len() as u64;
writer
.write_all(&vec![0u8; pad_len as usize])
.map_err(DnaError::IoError)?;
let mut total_written = header_bytes.len() + pad_len as usize;
for i in 0..NUM_SECTIONS {
if !sections_data[i].is_empty() {
let needed = self.header.sections[i].offset as usize - total_written;
if needed > 0 {
writer
.write_all(&vec![0u8; needed])
.map_err(DnaError::IoError)?;
total_written += needed;
}
writer
.write_all(§ions_data[i])
.map_err(DnaError::IoError)?;
total_written += sections_data[i].len();
}
}
Ok(total_written)
}
}
pub struct RvdnaReader {
pub header: RvdnaHeader,
data: Vec<u8>,
}
impl RvdnaReader {
pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
let header = RvdnaHeader::from_bytes(&data)?;
Ok(Self { header, data })
}
pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
let mut data = Vec::new();
reader.read_to_end(&mut data).map_err(DnaError::IoError)?;
Self::from_bytes(data)
}
pub fn read_sequence(&self) -> Result<DnaSequence> {
let section = &self.header.sections[SectionType::Sequence as usize];
if section.size == 0 {
return Err(DnaError::EmptySequence);
}
let start = section.offset as usize;
let packed_len =
u32::from_le_bytes(self.data[start..start + 4].try_into().unwrap()) as usize;
let mask_len =
u32::from_le_bytes(self.data[start + 4..start + 8].try_into().unwrap()) as usize;
let packed = &self.data[start + 8..start + 8 + packed_len];
let n_mask = &self.data[start + 8 + packed_len..start + 8 + packed_len + mask_len];
let bases = decode_2bit(packed, n_mask, self.header.sequence_length as usize);
Ok(DnaSequence::new(bases))
}
pub fn read_kmer_vectors(&self) -> Result<Vec<KmerVectorBlock>> {
let section = &self.header.sections[SectionType::KmerVectors as usize];
if section.size == 0 {
return Ok(Vec::new());
}
let start = section.offset as usize;
let count = u32::from_le_bytes(self.data[start..start + 4].try_into().unwrap()) as usize;
let mut blocks = Vec::with_capacity(count);
let mut offset = start + 4;
for _ in 0..count {
let block_len =
u32::from_le_bytes(self.data[offset..offset + 4].try_into().unwrap()) as usize;
offset += 4;
let block: KmerVectorBlock =
serde_json::from_slice(&self.data[offset..offset + block_len])
.map_err(|e| DnaError::PipelineError(e.to_string()))?;
blocks.push(block);
offset += block_len;
}
Ok(blocks)
}
pub fn read_attention(&self) -> Result<Option<SparseAttention>> {
let section = &self.header.sections[SectionType::AttentionWeights as usize];
if section.size == 0 {
return Ok(None);
}
let start = section.offset as usize;
let end = start + section.size as usize;
Ok(Some(SparseAttention::from_bytes(&self.data[start..end])?))
}
pub fn read_variants(&self) -> Result<Option<VariantTensor>> {
let section = &self.header.sections[SectionType::VariantTensor as usize];
if section.size == 0 {
return Ok(None);
}
let start = section.offset as usize;
let end = start + section.size as usize;
Ok(Some(VariantTensor::from_bytes(&self.data[start..end])?))
}
pub fn read_metadata(&self) -> Result<Option<serde_json::Value>> {
let section = &self.header.sections[SectionType::Metadata as usize];
if section.size == 0 {
return Ok(None);
}
let start = section.offset as usize;
let end = start + section.size as usize;
let meta: serde_json::Value = serde_json::from_slice(&self.data[start..end])
.map_err(|e| DnaError::PipelineError(e.to_string()))?;
Ok(Some(meta))
}
pub fn stats(&self) -> RvdnaStats {
let mut section_sizes = [0u64; NUM_SECTIONS];
for i in 0..NUM_SECTIONS {
section_sizes[i] = self.header.sections[i].size;
}
let total_size = self.data.len() as u64;
let seq_len = self.header.sequence_length;
let bits_per_base = if seq_len > 0 {
(section_sizes[0] as f64 * 8.0) / seq_len as f64
} else {
0.0
};
RvdnaStats {
total_size,
sequence_length: seq_len,
bits_per_base,
section_sizes,
compression_ratio: if seq_len > 0 {
seq_len as f64 / total_size as f64
} else {
0.0
},
}
}
}
#[derive(Debug, Clone)]
pub struct RvdnaStats {
pub total_size: u64,
pub sequence_length: u64,
pub bits_per_base: f64,
pub section_sizes: [u64; NUM_SECTIONS],
pub compression_ratio: f64,
}
pub fn fasta_to_rvdna(
sequence_str: &str,
k: u32,
vector_dims: u32,
block_size: u64,
) -> Result<Vec<u8>> {
let sequence = DnaSequence::from_str(sequence_str)?;
let metadata = serde_json::json!({
"format": "RVDNA",
"version": FORMAT_VERSION,
"source": "fasta_conversion",
"sequence_length": sequence.len(),
"kmer_k": k,
"vector_dimensions": vector_dims,
"block_size": block_size,
});
let mut writer = RvdnaWriter::new(&sequence, Codec::None)
.with_kmer_vectors(&sequence, k, vector_dims, block_size)?
.with_metadata(metadata);
let mut output = Vec::new();
writer.write(&mut output)?;
Ok(output)
}
const CRC32_TABLE: [u32; 256] = {
let mut table = [0u32; 256];
let mut i = 0u32;
while i < 256 {
let mut crc = i;
let mut j = 0;
while j < 8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
j += 1;
}
table[i as usize] = crc;
i += 1;
}
table
};
fn crc32_simple(data: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFFFFFF;
for &byte in data {
let idx = ((crc ^ byte as u32) & 0xFF) as usize;
crc = CRC32_TABLE[idx] ^ (crc >> 8);
}
!crc
}
fn align_up(value: u64, alignment: u64) -> u64 {
(value + alignment - 1) & !(alignment - 1)
}
fn nucleotide_to_2bit(n: Nucleotide) -> u8 {
match n {
Nucleotide::A => 0,
Nucleotide::C => 1,
Nucleotide::G => 2,
Nucleotide::T => 3,
Nucleotide::N => 0,
}
}
fn f32_to_f16(value: f32) -> u16 {
let bits = value.to_bits();
let sign = (bits >> 31) & 1;
let exponent = ((bits >> 23) & 0xFF) as i32;
let mantissa = bits & 0x7FFFFF;
if exponent == 0xFF {
return ((sign << 15) | 0x7C00 | (mantissa >> 13).min(1)) as u16;
}
let new_exp = exponent - 127 + 15;
if new_exp >= 31 {
return ((sign << 15) | 0x7C00) as u16;
}
if new_exp <= 0 {
return (sign << 15) as u16;
}
((sign << 15) | (new_exp as u32) << 10 | (mantissa >> 13)) as u16
}
fn f16_to_f32(half: u16) -> f32 {
let sign = ((half >> 15) & 1) as u32;
let exponent = ((half >> 10) & 0x1F) as u32;
let mantissa = (half & 0x3FF) as u32;
if exponent == 0x1F {
let bits = (sign << 31) | 0x7F800000 | (mantissa << 13);
return f32::from_bits(bits);
}
if exponent == 0 {
if mantissa == 0 {
return f32::from_bits(sign << 31); }
let bits = (sign << 31) | ((exponent + 127 - 15) << 23) | (mantissa << 13);
return f32::from_bits(bits);
}
let bits = (sign << 31) | ((exponent + 127 - 15) << 23) | (mantissa << 13);
f32::from_bits(bits)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_2bit_encoding_roundtrip() {
let bases = vec![
Nucleotide::A,
Nucleotide::C,
Nucleotide::G,
Nucleotide::T,
Nucleotide::A,
Nucleotide::N,
Nucleotide::G,
Nucleotide::T,
Nucleotide::C,
];
let (packed, mask) = encode_2bit(&bases);
let decoded = decode_2bit(&packed, &mask, bases.len());
assert_eq!(bases, decoded);
}
#[test]
fn test_2bit_compression_ratio() {
let bases: Vec<Nucleotide> = (0..1000)
.map(|i| match i % 4 {
0 => Nucleotide::A,
1 => Nucleotide::C,
2 => Nucleotide::G,
_ => Nucleotide::T,
})
.collect();
let (packed, _mask) = encode_2bit(&bases);
assert_eq!(packed.len(), 250); }
#[test]
fn test_quality_encoding_roundtrip() {
let qualities: Vec<u8> = (0..100).map(|i| (i % 42) as u8).collect();
let encoded = encode_quality(&qualities);
let decoded = decode_quality(&encoded, qualities.len());
assert_eq!(qualities, decoded);
}
#[test]
fn test_quality_compression_ratio() {
let qualities: Vec<u8> = vec![30; 100];
let encoded = encode_quality(&qualities);
assert!(
encoded.len() <= 75,
"6-bit should compress: {} bytes",
encoded.len()
);
}
#[test]
fn test_sparse_attention_roundtrip() {
let dense = vec![
0.0, 0.5, 0.0, 0.0, 0.3, 0.0, 0.0, 0.7, 0.0, 0.0, 0.9, 0.0, 0.0, 0.1, 0.0, 0.0,
];
let sparse = SparseAttention::from_dense(&dense, 4, 4, 0.05);
assert_eq!(sparse.nnz(), 5); assert!(sparse.sparsity() > 0.6);
let bytes = sparse.to_bytes();
let restored = SparseAttention::from_bytes(&bytes).unwrap();
assert_eq!(restored.nnz(), 5);
assert!((restored.get(0, 1) - 0.5).abs() < 1e-6);
assert!((restored.get(1, 3) - 0.7).abs() < 1e-6);
}
#[test]
fn test_variant_tensor_binary_search() {
let mut vt = VariantTensor::new();
vt.add_variant(100, Nucleotide::A, Nucleotide::G, 0.01, 0.99, 0.0, 40);
vt.add_variant(200, Nucleotide::C, Nucleotide::T, 0.0, 0.5, 0.5, 35);
vt.add_variant(300, Nucleotide::G, Nucleotide::A, 0.9, 0.1, 0.0, 50);
let gl = vt.get_likelihoods(200).unwrap();
assert!(gl[1] > 0.4); assert!(vt.get_likelihoods(150).is_none());
let bytes = vt.to_bytes();
let restored = VariantTensor::from_bytes(&bytes).unwrap();
assert_eq!(restored.len(), 3);
}
#[test]
fn test_f16_roundtrip() {
for &val in &[0.0f32, 1.0, -1.0, 0.5, 0.001, 100.0] {
let half = f32_to_f16(val);
let back = f16_to_f32(half);
let rel_err = if val.abs() > 0.0 {
(back - val).abs() / val.abs()
} else {
back.abs()
};
assert!(
rel_err < 0.01,
"f16 roundtrip failed for {}: got {}",
val,
back
);
}
}
#[test]
fn test_header_roundtrip() {
let mut header = RvdnaHeader::new(10000, Codec::None).with_quality();
header.sections[0] = SectionEntry {
offset: 192,
size: 2500,
};
header.sections[1] = SectionEntry {
offset: 2752,
size: 8192,
};
let bytes = header.to_bytes();
let restored = RvdnaHeader::from_bytes(&bytes).unwrap();
assert_eq!(restored.version, FORMAT_VERSION);
assert_eq!(restored.codec, Codec::None);
assert!(restored.has_quality());
assert_eq!(restored.sequence_length, 10000);
assert_eq!(restored.sections[0].offset, 192);
assert_eq!(restored.sections[0].size, 2500);
}
#[test]
fn test_full_rvdna_write_read() {
let bases: Vec<Nucleotide> = "ACGTACGTACGTACGTACGTACGTACGTACGT"
.chars()
.map(|c| match c {
'A' => Nucleotide::A,
'C' => Nucleotide::C,
'G' => Nucleotide::G,
'T' => Nucleotide::T,
_ => Nucleotide::N,
})
.collect();
let sequence = DnaSequence::new(bases);
let mut writer = RvdnaWriter::new(&sequence, Codec::None)
.with_metadata(serde_json::json!({"sample": "test"}));
let mut output = Vec::new();
let written = writer.write(&mut output).unwrap();
assert!(written > 0);
let reader = RvdnaReader::from_bytes(output).unwrap();
assert_eq!(reader.header.sequence_length, 32);
assert_eq!(reader.header.codec, Codec::None);
let restored_seq = reader.read_sequence().unwrap();
assert_eq!(restored_seq.len(), 32);
assert_eq!(restored_seq.to_string(), sequence.to_string());
let meta = reader.read_metadata().unwrap().unwrap();
assert_eq!(meta["sample"], "test");
let stats = reader.stats();
assert!(
stats.bits_per_base < 8.0,
"Should compress below 1 byte/base"
);
}
#[test]
fn test_rvdna_with_kmer_vectors() {
let seq = DnaSequence::from_str("ACGTACGTACGTACGTACGTACGTACGTACGT").unwrap();
let mut writer = RvdnaWriter::new(&seq, Codec::None)
.with_kmer_vectors(&seq, 11, 256, 32)
.unwrap();
let mut output = Vec::new();
writer.write(&mut output).unwrap();
let reader = RvdnaReader::from_bytes(output).unwrap();
let blocks = reader.read_kmer_vectors().unwrap();
assert!(!blocks.is_empty());
assert_eq!(blocks[0].k, 11);
assert_eq!(blocks[0].dimensions, 256);
assert!(blocks[0].quantized.is_some());
}
#[test]
fn test_fasta_to_rvdna_conversion() {
let fasta_seq = "ACGTACGTACGTACGTACGTACGTACGTACGT";
let rvdna_bytes = fasta_to_rvdna(fasta_seq, 11, 256, 1000).unwrap();
let reader = RvdnaReader::from_bytes(rvdna_bytes).unwrap();
let restored = reader.read_sequence().unwrap();
assert_eq!(restored.to_string(), fasta_seq);
let stats = reader.stats();
assert!(stats.total_size > 0);
assert!(
stats.bits_per_base < 8.0,
"Should beat 1-byte-per-base encoding, got {:.1} bits/base",
stats.bits_per_base
);
}
#[test]
fn test_kmer_vector_similarity() {
let seq1 = DnaSequence::from_str("ACGTACGTACGTACGTACGTACGTACGTACGT").unwrap();
let seq2 = DnaSequence::from_str("ACGTACGTACGTACGTACGTACGTACGTACGG").unwrap(); let seq3 = DnaSequence::from_str("TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT").unwrap();
let block1 = KmerVectorBlock::from_sequence(&seq1, 0, 32, 11, 256).unwrap();
let vec2 = seq2.to_kmer_vector(11, 256).unwrap();
let vec3 = seq3.to_kmer_vector(11, 256).unwrap();
let sim_similar = block1.cosine_similarity(&vec2);
let sim_different = block1.cosine_similarity(&vec3);
assert!(
sim_similar > sim_different,
"Similar sequence ({}) should score higher than different ({})",
sim_similar,
sim_different
);
}
}