use crate::{ENDMARKER, SOURCE_KEY, SOURCE_VALUE, REF_SAMPLE};
use crate::{Orientation, Pos};
use crate::bwt::{BWT, Record};
use crate::headers::{Header, GBWTPayload, MetadataPayload};
use crate::support::{Dictionary, StringIter, Tags};
use crate::support;
use simple_sds::serialize::{Serialize, Serializable};
use simple_sds::serialize;
use std::io::{Error, ErrorKind};
use std::iter::FusedIterator;
use std::ops::Range;
use std::{fmt, io, slice, usize};
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GBWT {
header: Header<GBWTPayload>,
tags: Tags,
bwt: BWT,
endmarker: Vec<Pos>,
metadata: Option<Metadata>,
}
impl GBWT {
#[inline]
pub fn len(&self) -> usize {
self.header.payload().size
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn sequences(&self) -> usize {
self.header.payload().sequences
}
#[inline]
pub fn alphabet_size(&self) -> usize {
self.header.payload().alphabet_size
}
#[inline]
pub fn alphabet_offset(&self) -> usize {
self.header.payload().offset
}
#[inline]
pub fn effective_size(&self) -> usize {
self.alphabet_size() - self.alphabet_offset()
}
#[inline]
pub fn first_node(&self) -> usize {
self.alphabet_offset() + 1
}
#[inline]
pub fn node_to_record(&self, node_id: usize) -> usize {
node_id - self.alphabet_offset()
}
#[inline]
pub fn record_to_node(&self, record_id: usize) -> usize {
record_id + self.alphabet_offset()
}
#[inline]
pub fn has_node(&self, id: usize) -> bool {
id > self.alphabet_offset() && id < self.alphabet_size()
}
#[inline]
pub fn is_bidirectional(&self) -> bool {
self.header.is_set(GBWTPayload::FLAG_BIDIRECTIONAL)
}
}
impl GBWT {
pub fn has_metadata(&self) -> bool {
self.header.is_set(GBWTPayload::FLAG_METADATA)
}
pub fn metadata(&self) -> Option<&Metadata> {
self.metadata.as_ref()
}
pub fn tags(&self) -> &Tags {
&self.tags
}
pub fn tags_mut(&mut self) -> &mut Tags {
&mut self.tags
}
}
impl AsRef<BWT> for GBWT {
fn as_ref(&self) -> &BWT {
&self.bwt
}
}
impl GBWT {
pub fn start(&self, id: usize) -> Option<Pos> {
if id < self.endmarker.len() && self.endmarker[id].node != ENDMARKER {
Some(self.endmarker[id])
} else {
None
}
}
pub fn forward(&self, pos: Pos) -> Option<Pos> {
if pos.node < self.first_node() {
return None;
}
let record = self.bwt.record(self.node_to_record(pos.node))?;
record.lf(pos.offset)
}
pub fn backward(&self, pos: Pos) -> Option<Pos> {
assert!(self.is_bidirectional(), "Following sequences backward requires a bidirectional GBWT");
if pos.node <= self.first_node() {
return None;
}
let reverse_id = self.node_to_record(support::flip_node(pos.node));
let record = self.bwt.record(reverse_id)?;
let predecessor = record.predecessor_at(pos.offset)?;
let pred_record = self.bwt.record(self.node_to_record(predecessor))?;
let offset = pred_record.offset_to(pos)?;
Some(Pos::new(predecessor, offset))
}
pub fn sequence(&self, id: usize) -> Option<SequenceIter> {
if id >= self.sequences() {
return None;
}
Some(SequenceIter {
parent: self,
next: self.start(id),
})
}
}
impl GBWT {
pub fn find(&self, node: usize) -> Option<SearchState> {
if node < self.first_node() {
return None;
}
if let Some(record) = self.bwt.record(self.node_to_record(node)) {
return Some(SearchState {
node,
range: 0..record.len(),
});
}
None
}
pub fn extend(&self, state: &SearchState, node: usize) -> Option<SearchState> {
if node < self.first_node() {
return None;
}
if let Some(record) = self.bwt.record(self.node_to_record(state.node)) {
if let Some(range) = record.follow(state.range.clone(), node) {
return Some(SearchState {
node, range,
})
}
}
None
}
pub fn bd_find(&self, node: usize) -> Option<BidirectionalState> {
assert!(self.is_bidirectional(), "Bidirectional search requires a bidirectional GBWT");
if let Some(state) = self.find(node) {
let reverse = SearchState {
node: support::flip_node(state.node),
range: state.range.clone(),
};
return Some(BidirectionalState {
forward: state,
reverse,
});
}
None
}
pub fn extend_forward(&self, state: &BidirectionalState, node: usize) -> Option<BidirectionalState> {
assert!(self.is_bidirectional(), "Bidirectional search requires a bidirectional GBWT");
if node < self.first_node() {
return None;
}
let record = self.bwt.record(self.node_to_record(state.forward.node))?;
Self::bd_internal(&record, state, node)
}
pub fn extend_backward(&self, state: &BidirectionalState, node: usize) -> Option<BidirectionalState> {
if let Some(result) = self.extend_forward(&state.flip(), support::flip_node(node)) {
return Some(result.flip());
}
None
}
#[doc(hidden)]
pub fn bd_internal(record: &Record, state: &BidirectionalState, node: usize) -> Option<BidirectionalState> {
let (range, offset) = record.bd_follow(state.forward.range.clone(), node)?;
let forward = SearchState {
node, range,
};
let pos = state.reverse.range.start + offset;
let reverse = SearchState {
node: state.reverse.node,
range: pos..pos + forward.len(),
};
Some(BidirectionalState {
forward, reverse,
})
}
}
impl Serialize for GBWT {
fn serialize_header<T: io::Write>(&self, writer: &mut T) -> io::Result<()> {
self.header.serialize(writer)
}
fn serialize_body<T: io::Write>(&self, writer: &mut T) -> io::Result<()> {
self.tags.serialize(writer)?;
self.bwt.serialize(writer)?;
serialize::absent_option(writer)?; self.metadata.serialize(writer)?;
Ok(())
}
fn load<T: io::Read>(reader: &mut T) -> io::Result<Self> {
let header = Header::<GBWTPayload>::load(reader)?;
if let Err(msg) = header.validate() {
return Err(Error::new(ErrorKind::InvalidData, msg));
}
let mut tags = Tags::load(reader)?;
tags.insert(SOURCE_KEY, SOURCE_VALUE);
let bwt = BWT::load(reader)?;
let endmarker = if bwt.is_empty() { Vec::new() } else { bwt.record(ENDMARKER).unwrap().decompress() };
serialize::skip_option(reader)?;
let metadata = Option::<Metadata>::load(reader)?;
if header.is_set(GBWTPayload::FLAG_METADATA) != metadata.is_some() {
return Err(Error::new(ErrorKind::InvalidData, "GBWT: Invalid metadata flag in the header"));
}
if let Some(meta) = metadata.as_ref() {
if meta.has_path_names() {
let expected = if header.is_set(GBWTPayload::FLAG_BIDIRECTIONAL) { header.payload().sequences / 2 } else { header.payload().sequences };
if meta.paths() > 0 && meta.paths() != expected {
return Err(Error::new(ErrorKind::InvalidData, "GBWT: Invalid path count in the metadata"));
}
}
}
Ok(GBWT {
header, tags, bwt, endmarker, metadata,
})
}
fn size_in_elements(&self) -> usize {
self.header.size_in_elements() + self.tags.size_in_elements() + self.bwt.size_in_elements() + serialize::absent_option_size() + self.metadata.size_in_elements()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SearchState {
pub node: usize,
pub range: Range<usize>,
}
impl SearchState {
#[inline]
pub fn len(&self) -> usize {
self.range.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.range.is_empty()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BidirectionalState {
pub forward: SearchState,
pub reverse: SearchState,
}
impl BidirectionalState {
#[inline]
pub fn len(&self) -> usize {
self.forward.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.forward.is_empty()
}
pub fn flip(&self) -> BidirectionalState {
BidirectionalState {
forward: self.reverse.clone(),
reverse: self.forward.clone(),
}
}
#[inline]
pub fn from(&self) -> (usize, Orientation) {
support::decode_node(support::flip_node(self.reverse.node))
}
#[inline]
pub fn to(&self) -> (usize, Orientation) {
support::decode_node(self.forward.node)
}
}
#[derive(Clone, Debug)]
pub struct SequenceIter<'a> {
parent: &'a GBWT,
next: Option<Pos>,
}
impl<'a> Iterator for SequenceIter<'a> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if let Some(pos) = self.next {
self.next = self.parent.forward(pos);
Some(pos.node)
} else {
None
}
}
}
impl<'a> FusedIterator for SequenceIter<'a> {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Metadata {
header: Header<MetadataPayload>,
path_names: Vec<PathName>,
sample_names: Dictionary,
contig_names: Dictionary,
}
impl Metadata {
pub fn has_path_names(&self) -> bool {
self.header.is_set(MetadataPayload::FLAG_PATH_NAMES)
}
pub fn paths(&self) -> usize {
self.path_names.len()
}
pub fn path(&self, id: usize) -> Option<PathName> {
if id < self.paths() {
Some(self.path_names[id])
} else {
None
}
}
pub fn find_path(&self, name: &FullPathName) -> Option<usize> {
if !self.has_path_names() || !self.has_sample_names() || !self.has_contig_names() {
return None;
}
let path_name = PathName::from_fields(
self.sample_id(&name.sample)?,
self.contig_id(&name.contig)?,
name.haplotype,
name.fragment,
);
for (i, path) in self.path_names.iter().enumerate() {
if *path == path_name {
return Some(i);
}
}
None
}
pub fn find_fragment(&self, name: &FullPathName) -> Option<usize> {
if !self.has_path_names() || !self.has_sample_names() || !self.has_contig_names() {
return None;
}
let path_name = PathName::from_fields(
self.sample_id(&name.sample)?,
self.contig_id(&name.contig)?,
name.haplotype,
name.fragment,
);
let mut prev_fragment = 0;
let mut result: Option<usize> = None;
for (i, path) in self.path_names.iter().enumerate() {
if path.sample == path_name.sample && path.contig == path_name.contig && path.phase == path_name.phase && path.fragment <= path_name.fragment {
if result.is_none() || (result.is_some() && path.fragment > prev_fragment) {
prev_fragment = path.fragment;
result = Some(i);
}
}
}
result
}
pub fn pan_sn_path(&self, id: usize) -> Option<String> {
let path_name = self.path(id)?;
let result = format!("{}#{}#{}", self.sample_name(path_name.sample()), path_name.phase(), self.contig_name(path_name.contig()));
Some(result)
}
pub fn path_iter(&self) -> slice::Iter<PathName> {
self.path_names.iter()
}
}
impl Metadata {
pub fn has_sample_names(&self) -> bool {
self.header.is_set(MetadataPayload::FLAG_SAMPLE_NAMES)
}
pub fn samples(&self) -> usize {
self.header.payload().sample_count
}
pub fn haplotypes(&self) -> usize {
self.header.payload().haplotype_count
}
pub fn sample(&self, id: usize) -> Option<&str> {
if self.has_sample_names() && id < self.samples() {
self.sample_names.str(id).ok()
} else {
None
}
}
pub fn sample_name(&self, id: usize) -> String {
if let Some(name) = self.sample(id) {
name.to_string()
} else {
id.to_string()
}
}
pub fn sample_id(&self, name: &str) -> Option<usize> {
self.sample_names.id(name)
}
pub fn sample_iter(&self) -> StringIter {
self.sample_names.as_ref().iter()
}
}
impl Metadata {
pub fn has_contig_names(&self) -> bool {
self.header.is_set(MetadataPayload::FLAG_CONTIG_NAMES)
}
pub fn contigs(&self) -> usize {
self.header.payload().contig_count
}
pub fn contig(&self, id: usize) -> Option<&str> {
if self.has_contig_names() && id < self.contigs() {
self.contig_names.str(id).ok()
} else {
None
}
}
pub fn contig_name(&self, id: usize) -> String {
if let Some(name) = self.contig(id) {
name.to_string()
} else {
id.to_string()
}
}
pub fn contig_id(&self, name: &str) -> Option<usize> {
self.contig_names.id(name)
}
pub fn contig_iter(&self) -> StringIter {
self.contig_names.as_ref().iter()
}
}
impl Serialize for Metadata {
fn serialize_header<T: io::Write>(&self, writer: &mut T) -> io::Result<()> {
self.header.serialize(writer)
}
fn serialize_body<T: io::Write>(&self, writer: &mut T) -> io::Result<()> {
self.path_names.serialize(writer)?;
self.sample_names.serialize(writer)?;
self.contig_names.serialize(writer)?;
Ok(())
}
fn load<T: io::Read>(reader: &mut T) -> io::Result<Self> {
let header = Header::<MetadataPayload>::load(reader)?;
if let Err(msg) = header.validate() {
return Err(Error::new(ErrorKind::InvalidData, msg));
}
let path_names = Vec::<PathName>::load(reader)?;
if header.is_set(MetadataPayload::FLAG_PATH_NAMES) == path_names.is_empty() {
return Err(Error::new(ErrorKind::InvalidData, "Metadata: Path name flag does not match the presence of path names"));
}
let sample_names = Dictionary::load(reader)?;
if header.is_set(MetadataPayload::FLAG_SAMPLE_NAMES) {
if header.payload().sample_count != sample_names.len() {
return Err(Error::new(ErrorKind::InvalidData, "Metadata: Sample count does not match the number of sample names"));
}
} else if !sample_names.is_empty() {
return Err(Error::new(ErrorKind::InvalidData, "Metadata: Sample names are present without the sample name flag"));
}
let contig_names = Dictionary::load(reader)?;
if header.is_set(MetadataPayload::FLAG_CONTIG_NAMES) {
if header.payload().contig_count != contig_names.len() {
return Err(Error::new(ErrorKind::InvalidData, "Metadata: Contig count does not match the number of contig names"));
}
} else if !contig_names.is_empty() {
return Err(Error::new(ErrorKind::InvalidData, "Metadata: Contig names are present without the contig name flag"));
}
Ok(Metadata {
header, path_names, sample_names, contig_names,
})
}
fn size_in_elements(&self) -> usize {
self.header.size_in_elements() + self.path_names.size_in_elements() + self.sample_names.size_in_elements() + self.contig_names.size_in_elements()
}
}
#[repr(C)]
#[derive(Copy, Clone, Default, Debug, Hash, PartialEq, Eq)]
pub struct PathName {
pub sample: u32,
pub contig: u32,
pub phase: u32,
pub fragment: u32,
}
impl PathName {
pub fn new() -> Self {
Self::default()
}
pub fn from_fields(sample: usize, contig: usize, phase: usize, fragment: usize) -> Self {
PathName {
sample: sample as u32,
contig: contig as u32,
phase: phase as u32,
fragment: fragment as u32,
}
}
pub fn sample(&self) -> usize {
self.sample as usize
}
pub fn contig(&self) -> usize {
self.contig as usize
}
pub fn phase(&self) -> usize {
self.phase as usize
}
pub fn fragment(&self) -> usize {
self.fragment as usize
}
}
impl Serializable for PathName {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FullPathName {
pub sample: String,
pub contig: String,
pub haplotype: usize,
pub fragment: usize,
}
impl FullPathName {
pub fn from_metadata(metadata: &Metadata, path_id: usize) -> Option<Self> {
let name = metadata.path(path_id)?;
let sample = metadata.sample_name(name.sample());
let contig = metadata.contig_name(name.contig());
Some(FullPathName {
sample, contig,
haplotype: name.phase(),
fragment: name.fragment(),
})
}
pub fn generic(name: &str) -> Self {
FullPathName {
sample: String::from(REF_SAMPLE),
contig: String::from(name),
haplotype: 0,
fragment: 0,
}
}
pub fn reference(sample: &str, contig: &str) -> Self {
FullPathName {
sample: String::from(sample),
contig: String::from(contig),
haplotype: 0,
fragment: 0,
}
}
pub fn haplotype(sample: &str, contig: &str, haplotype: usize, fragment: usize) -> Self {
FullPathName {
sample: String::from(sample),
contig: String::from(contig),
haplotype,
fragment,
}
}
pub fn pan_sn_name(&self) -> String {
format!("{}#{}#{}", self.sample, self.haplotype, self.contig)
}
pub fn path_fragment_name(&self, end: usize) -> String {
format!("{}#{}#{}[{}-{}]", self.sample, self.haplotype, self.contig, self.fragment, end)
}
}
impl fmt::Display for FullPathName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.sample == REF_SAMPLE {
write!(f, "{}", self.contig)
} else if self.haplotype == 0 && self.fragment == 0 {
write!(f, "{}#{}", self.sample, self.contig)
} else {
write!(f, "{}#{}#{}@{}", self.sample, self.haplotype, self.contig, self.fragment)
}
}
}