Skip to main content

bidirected_adjacency_array/io/
gfa1.rs

1use 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                    // Parse edge line.
107                    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 name index before constructing graph to save RAM.
169        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        // Write header.
179        writeln!(writer, "H\tVN:Z:1.0")?;
180
181        // Write nodes.
182        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        // Write edges.
188        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            // In mathematical notation, traversing an edge from a to b means using edge (a, \hat{b}).
195            // But in GFA1, this means using edge (a, b), where both signs are unchanged.
196            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}