bidirected_adjacency_array/io/
gfa1.rs1use std::{
2 borrow::Cow,
3 collections::{HashMap, HashSet},
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 let mut unsupported_line_types = HashSet::new();
71
72 for line in reader.lines() {
73 let line = line?;
74 let line = line.trim().split('\t').collect::<Vec<_>>();
75
76 match line[0] {
77 "H" => {
78 if is_header_allowed {
79 if let Some(&version) = line.get(1) {
80 if version.starts_with("VN:Z:") {
81 let version = version.trim_start_matches("VN:Z:");
82 if version != "1.0" {
83 warn!(
84 "Unsupported GFA version {version:?}, expected \"1.0\". Attempting to parse anyway, but this may lead to errors or a wrong graph.",
85 );
86 }
87 } else {
88 warn!(
89 "GFA header line has unrecognized version information, expected \"VN:Z:1.0\", but got \"{}\"",
90 version
91 );
92 }
93 } else {
94 warn!(
95 "GFA header line is missing version information, expected \"VN:Z:1.0\""
96 );
97 }
98 } else {
99 return Err(GfaReadError::WronglyPositionedHeader);
100 }
101 }
102
103 "S" => {
104 let name = line
105 .get(1)
106 .ok_or(GfaReadError::MissingSequenceNameInSLine)?
107 .to_string();
108 let sequence = line.get(2).unwrap_or(&"").to_string();
109 let node = nodes.push(
110 PlainGfaNodeData {
111 name: name.clone(),
112 sequence,
113 }
114 .into(),
115 );
116 node_name_to_node.insert(name.clone(), node);
117 }
118
119 "L" => {
120 let from = line.get(1).ok_or(GfaReadError::LLineTooShort)?.to_string();
122 let from_forward = match *line.get(2).ok_or(GfaReadError::LLineTooShort)? {
123 "+" => true,
124 "-" => false,
125 other => return Err(GfaReadError::UnknownGfaNodeSign(other.to_string())),
126 };
127 let to = line.get(3).ok_or(GfaReadError::LLineTooShort)?.to_string();
128 let to_forward = match *line.get(4).ok_or(GfaReadError::LLineTooShort)? {
129 "+" => true,
130 "-" => false,
131 other => return Err(GfaReadError::UnknownGfaNodeSign(other.to_string())),
132 };
133 let overlap_str = line.get(5).unwrap_or(&"0M");
134 let overlap = overlap_str
135 .trim_end_matches('M')
136 .parse::<u16>()
137 .unwrap_or(0);
138
139 edges.push(UnresolvedBidirectedEdge {
140 from,
141 from_forward,
142 to,
143 to_forward,
144 data: PlainGfaEdgeData { overlap },
145 });
146 }
147
148 other => {
149 if unsupported_line_types.insert(other.to_string()) {
150 warn!(
151 "Unsupported GFA line type: {other:?}. Subsequent lines of this type will be ignored.",
152 );
153 }
154 }
155 }
156
157 is_header_allowed = false;
158 }
159
160 let edges = edges
161 .into_values_iter()
162 .map(|edge| {
163 let from = node_name_to_node
164 .get(&edge.from)
165 .copied()
166 .ok_or(GfaReadError::UnknownNodeName(edge.from))?;
167 let to = node_name_to_node
168 .get(&edge.to)
169 .copied()
170 .ok_or(GfaReadError::UnknownNodeName(edge.to))?;
171
172 let from_forward = edge.from_forward;
173 let to_forward = edge.to_forward;
174 let data = EdgeData::from(edge.data);
175
176 Result::<_, GfaReadError>::Ok(BidirectedEdge {
177 from,
178 from_forward,
179 to,
180 to_forward,
181 data,
182 })
183 })
184 .collect::<Result<Vec<_>, GfaReadError>>()?;
185
186 drop(node_name_to_node);
188 Ok(BidirectedAdjacencyArray::new(nodes, edges.into()))
189 }
190}
191
192impl<IndexType: GraphIndexInteger, NodeData: GfaNodeData, EdgeData: GfaEdgeData>
193 BidirectedAdjacencyArray<IndexType, NodeData, EdgeData>
194{
195 pub fn write_gfa1(&self, mut writer: impl Write) -> Result<(), std::io::Error> {
196 writeln!(writer, "H\tVN:Z:1.0")?;
198
199 for node in self.iter_nodes() {
201 let node_data = self.node_data(node);
202 writeln!(writer, "S\t{}\t{}", node_data.name(), node_data.sequence())?;
203 }
204
205 for edge in self.iter_edges() {
207 let edge_data = self.edge(edge);
208
209 let from_node_name = self.node_data(edge_data.from().into_bidirected()).name();
210 let to_node_name = self.node_data(edge_data.to().into_bidirected()).name();
211
212 let from_node_sign = if edge_data.from().is_forward() {
215 "+"
216 } else {
217 "-"
218 };
219 let to_node_sign = if edge_data.to().is_forward() {
220 "+"
221 } else {
222 "-"
223 };
224
225 let overlap = edge_data.data().overlap();
226
227 writeln!(
228 writer,
229 "L\t{from_node_name}\t{from_node_sign}\t{to_node_name}\t{to_node_sign}\t{overlap}M",
230 )?;
231 }
232
233 Ok(())
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
238pub struct PlainGfaNodeData {
239 name: String,
240 sequence: String,
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
244pub struct PlainGfaEdgeData {
245 overlap: u16,
246}
247
248impl PlainGfaNodeData {
249 pub fn new(name: impl ToString, sequence: impl ToString) -> Self {
250 Self {
251 name: name.to_string(),
252 sequence: sequence.to_string(),
253 }
254 }
255}
256
257impl GfaNodeData for PlainGfaNodeData {
258 fn name(&'_ self) -> Cow<'_, str> {
259 Cow::Borrowed(&self.name)
260 }
261
262 fn sequence(&'_ self) -> Cow<'_, str> {
263 Cow::Borrowed(&self.sequence)
264 }
265}
266
267impl PlainGfaEdgeData {
268 pub fn new(overlap: u16) -> Self {
269 Self { overlap }
270 }
271}
272
273impl GfaEdgeData for PlainGfaEdgeData {
274 fn overlap(&self) -> u16 {
275 self.overlap
276 }
277}