bidirected_adjacency_array/io/
gfa1.rs1use std::{
2 borrow::Cow,
3 collections::HashMap,
4 fmt::Debug,
5 io::{BufRead, Write},
6};
7
8use log::warn;
9use tagged_vec::TaggedVec;
10
11use crate::{
12 graph::{BidirectedAdjacencyArray, BidirectedEdge},
13 index::{EdgeIndex, GraphIndexInteger, NodeIndex},
14};
15
16#[cfg(test)]
17mod tests;
18
19pub trait GfaNodeData {
20 fn name(&'_ self) -> Cow<'_, str>;
21 fn sequence(&'_ self) -> Cow<'_, str>;
22}
23
24pub trait GfaEdgeData {
25 fn overlap(&self) -> u16;
26}
27
28#[derive(thiserror::Error, Debug)]
29pub enum GfaReadError {
30 #[error("I/O error: {0}")]
31 IoError(#[from] std::io::Error),
32
33 #[error("a header line was found after other lines")]
34 WronglyPositionedHeader,
35
36 #[error("an S line is missing the sequence name")]
37 MissingSequenceNameInSLine,
38
39 #[error("an L line is missing the four fields specifying the edge endpoints")]
40 LLineTooShort,
41
42 #[error("unknown node name '{0}' in an L line")]
43 UnknownNodeName(String),
44
45 #[error("unknown sign '{0}' in an L line")]
46 UnknownGfaNodeSign(String),
47}
48
49struct UnresolvedBidirectedEdge {
50 from: String,
51 from_forward: bool,
52 to: String,
53 to_forward: bool,
54 data: PlainGfaEdgeData,
55}
56
57impl<
58 IndexType: GraphIndexInteger,
59 NodeData: From<PlainGfaNodeData>,
60 EdgeData: From<PlainGfaEdgeData>,
61> BidirectedAdjacencyArray<IndexType, NodeData, EdgeData>
62{
63 pub fn read_gfa1(
64 reader: impl BufRead,
65 ) -> Result<BidirectedAdjacencyArray<IndexType, NodeData, EdgeData>, GfaReadError> {
66 let mut node_name_to_node = HashMap::new();
67 let mut nodes = TaggedVec::<NodeIndex<IndexType>, _>::new();
68 let mut edges = TaggedVec::<EdgeIndex<IndexType>, _>::new();
69 let mut is_header_allowed = true;
70
71 for line in reader.lines() {
72 let line = line?;
73 let line = line.trim().split('\t').collect::<Vec<_>>();
74
75 match line[0] {
76 "H" => {
77 if is_header_allowed {
78 if line.get(1) != Some(&"VN:Z:1.0") {
79 warn!(
80 "Unsupported GFA version {:?}, expected \"VN:Z:1.0\"",
81 line.get(1),
82 );
83 }
84 } else {
85 return Err(GfaReadError::WronglyPositionedHeader);
86 }
87 }
88
89 "S" => {
90 let name = line
91 .get(1)
92 .ok_or(GfaReadError::MissingSequenceNameInSLine)?
93 .to_string();
94 let sequence = line.get(2).unwrap_or(&"").to_string();
95 let node = nodes.push(
96 PlainGfaNodeData {
97 name: name.clone(),
98 sequence,
99 }
100 .into(),
101 );
102 node_name_to_node.insert(name.clone(), node);
103 }
104
105 "L" => {
106 let from = line.get(1).ok_or(GfaReadError::LLineTooShort)?.to_string();
108 let from_forward = match *line.get(2).ok_or(GfaReadError::LLineTooShort)? {
109 "+" => true,
110 "-" => false,
111 other => return Err(GfaReadError::UnknownGfaNodeSign(other.to_string())),
112 };
113 let to = line.get(3).ok_or(GfaReadError::LLineTooShort)?.to_string();
114 let to_forward = match *line.get(4).ok_or(GfaReadError::LLineTooShort)? {
115 "+" => true,
116 "-" => false,
117 other => return Err(GfaReadError::UnknownGfaNodeSign(other.to_string())),
118 };
119 let overlap_str = line.get(5).unwrap_or(&"0M");
120 let overlap = overlap_str
121 .trim_end_matches('M')
122 .parse::<u16>()
123 .unwrap_or(0);
124
125 edges.push(UnresolvedBidirectedEdge {
126 from,
127 from_forward,
128 to,
129 to_forward,
130 data: PlainGfaEdgeData { overlap },
131 });
132 }
133
134 other => {
135 warn!("Unsupported GFA line type: {}", other);
136 }
137 }
138
139 is_header_allowed = false;
140 }
141
142 let edges = edges
143 .into_values_iter()
144 .map(|edge| {
145 let from = node_name_to_node
146 .get(&edge.from)
147 .copied()
148 .ok_or(GfaReadError::UnknownNodeName(edge.from))?;
149 let to = node_name_to_node
150 .get(&edge.to)
151 .copied()
152 .ok_or(GfaReadError::UnknownNodeName(edge.to))?;
153
154 let from_forward = edge.from_forward;
155 let to_forward = edge.to_forward;
156 let data = EdgeData::from(edge.data);
157
158 Result::<_, GfaReadError>::Ok(BidirectedEdge {
159 from,
160 from_forward,
161 to,
162 to_forward,
163 data,
164 })
165 })
166 .collect::<Result<Vec<_>, GfaReadError>>()?;
167
168 drop(node_name_to_node);
170 Ok(BidirectedAdjacencyArray::new(nodes, edges.into()))
171 }
172}
173
174impl<IndexType: GraphIndexInteger, NodeData: GfaNodeData, EdgeData: GfaEdgeData>
175 BidirectedAdjacencyArray<IndexType, NodeData, EdgeData>
176{
177 pub fn write_gfa1(&self, mut writer: impl Write) -> Result<(), std::io::Error> {
178 writeln!(writer, "H\tVN:Z:1.0")?;
180
181 for node in self.iter_nodes() {
183 let node_data = self.node_data(node);
184 writeln!(writer, "S\t{}\t{}", node_data.name(), node_data.sequence())?;
185 }
186
187 for edge in self.iter_edges() {
189 let edge_data = self.edge(edge);
190
191 let from_node_name = self.node_data(edge_data.from().into_bidirected()).name();
192 let to_node_name = self.node_data(edge_data.to().into_bidirected()).name();
193
194 let from_node_sign = if edge_data.from().is_forward() {
197 "+"
198 } else {
199 "-"
200 };
201 let to_node_sign = if edge_data.to().is_forward() {
202 "+"
203 } else {
204 "-"
205 };
206
207 let overlap = edge_data.data().overlap();
208
209 writeln!(
210 writer,
211 "L\t{from_node_name}\t{from_node_sign}\t{to_node_name}\t{to_node_sign}\t{overlap}M",
212 )?;
213 }
214
215 Ok(())
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
220pub struct PlainGfaNodeData {
221 name: String,
222 sequence: String,
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
226pub struct PlainGfaEdgeData {
227 overlap: u16,
228}
229
230impl PlainGfaNodeData {
231 pub fn new(name: impl ToString, sequence: impl ToString) -> Self {
232 Self {
233 name: name.to_string(),
234 sequence: sequence.to_string(),
235 }
236 }
237}
238
239impl GfaNodeData for PlainGfaNodeData {
240 fn name(&'_ self) -> Cow<'_, str> {
241 Cow::Borrowed(&self.name)
242 }
243
244 fn sequence(&'_ self) -> Cow<'_, str> {
245 Cow::Borrowed(&self.sequence)
246 }
247}
248
249impl PlainGfaEdgeData {
250 pub fn new(overlap: u16) -> Self {
251 Self { overlap }
252 }
253}
254
255impl GfaEdgeData for PlainGfaEdgeData {
256 fn overlap(&self) -> u16 {
257 self.overlap
258 }
259}