1
//! Authors: Maurice Laveaux and Sjef van Loo
2

            
3
use std::io::BufWriter;
4
use std::io::Read;
5
use std::io::Write;
6

            
7
use itertools::Itertools;
8
use log::info;
9
use regex::Regex;
10
use streaming_iterator::StreamingIterator;
11
use thiserror::Error;
12

            
13
use merc_io::LineIterator;
14
use merc_io::TimeProgress;
15
use merc_utilities::MercError;
16

            
17
use crate::PG;
18
use crate::ParityGame;
19
use crate::ParityGameBuilder;
20
use crate::Player;
21
use crate::Priority;
22
use crate::VertexIndex;
23

            
24
#[derive(Error, Debug)]
25
pub enum IOError {
26
    #[error("Invalid .pg header {0}")]
27
    InvalidHeader(&'static str),
28

            
29
    #[error("Invalid line {0}")]
30
    InvalidLine(&'static str),
31
}
32

            
33
/// Reads a parity game in textual PGSolver `.pg` format from the given reader.
34
///
35
/// # Details
36
///
37
/// The format starts with a header, followed by the vertices
38
///
39
/// `parity <num_of_vertices>;`
40
/// `<index> <priority> <owner> <outgoing_vertex>, <outgoing_vertex>, ...;`
41
101
pub fn read_pg<R: Read>(reader: R) -> Result<ParityGame, MercError> {
42
101
    info!("Reading parity game in .pg format...");
43

            
44
101
    let mut lines = LineIterator::new(reader);
45
101
    lines.advance();
46
101
    let header = lines
47
101
        .get()
48
101
        .ok_or(IOError::InvalidHeader("The first line should be the header"))?;
49

            
50
    // Read the header
51
101
    let header_regex = Regex::new(r#"parity\s+([0-9]+)\s*;"#).expect("Regex compilation should not fail");
52

            
53
101
    let (_, [num_of_vertices_txt]) = header_regex
54
101
        .captures(header)
55
101
        .ok_or(IOError::InvalidHeader("does not match parity <num_of_vertices>;"))?
56
101
        .extract();
57

            
58
101
    let num_of_vertices: usize = num_of_vertices_txt.parse()?;
59
101
    let progress = TimeProgress::new(
60
        |(amount, total): (usize, usize)| info!("Read {} vertices ({}%)...", amount, amount * 100 / total),
61
        1,
62
    );
63

            
64
    // Collect the data in a builder and remove duplicate edges at the end.
65
101
    let mut builder = ParityGameBuilder::with_capacity(VertexIndex::new(0), num_of_vertices);
66
101
    if num_of_vertices > 0 {
67
101
        builder.add_vertex(VertexIndex::new(num_of_vertices - 1), Player::Even, Priority::new(0));
68
101
    }
69

            
70
101
    let mut vertex_count = 0;
71
8103
    while let Some(line) = lines.next() {
72
        // Parse the line: <index> <priority> <owner> <outgoing_vertex>, <outgoing_vertex>, ...;
73
8002
        let mut parts = line.split_whitespace();
74

            
75
8002
        let index: usize = parts
76
8002
            .next()
77
8002
            .ok_or(IOError::InvalidLine("Expected at least <index> ...;"))?
78
8002
            .parse()?;
79
8002
        let vertex_priority: usize = parts
80
8002
            .next()
81
8002
            .ok_or(IOError::InvalidLine("Expected at least <index> <priority> ...;"))?
82
8002
            .parse()?;
83
8002
        let vertex_owner: u8 = parts
84
8002
            .next()
85
8002
            .ok_or(IOError::InvalidLine(
86
8002
                "Expected at least <index> <priority> <owner> ...;",
87
8002
            ))?
88
8002
            .parse()?;
89

            
90
8002
        let vertex = VertexIndex::new(index);
91
8002
        builder.add_vertex(vertex, Player::from_index(vertex_owner), Priority::new(vertex_priority));
92

            
93
14949
        for successors in parts {
94
            // Parse successors (remaining parts, removing trailing semicolon)
95
14949
            for successor in successors
96
14949
                .trim_end_matches(';')
97
14949
                .split(',')
98
21896
                .filter(|s| !s.trim().is_empty())
99
14949
                .map(|s| s.trim().parse())
100
            {
101
14949
                let successor = successor?;
102
14949
                builder.add_edge(vertex, VertexIndex::new(successor));
103
            }
104
        }
105

            
106
8002
        progress.print((vertex_count + 1, num_of_vertices));
107
8002
        vertex_count += 1;
108
    }
109

            
110
101
    Ok(builder.finish(true, true))
111
101
}
112

            
113
/// Writes the given parity game to the given writer in .pg format.
114
100
pub fn write_pg<W: Write, G: PG>(writer: W, game: &G) -> Result<(), MercError> {
115
100
    info!("Writing parity game to .pg format...");
116
100
    let mut writer = BufWriter::new(writer);
117

            
118
100
    let progress = TimeProgress::new(
119
        |(index, total): (usize, usize)| info!("Wrote {} vertices ({}%)...", index, index * 100 / total),
120
        1,
121
    );
122

            
123
100
    writeln!(writer, "parity {};", game.num_of_vertices())?;
124
5000
    for v in game.iter_vertices() {
125
5000
        let prio = game.priority(v);
126
5000
        let owner = game.owner(v).to_index();
127

            
128
5000
        write!(writer, "{} {} {} ", v.value(), prio.value(), owner)?;
129
5000
        write!(
130
5000
            writer,
131
            "{}",
132
10981
            game.outgoing_edges(v).map(|edge| edge.to().value()).format(", ")
133
        )?;
134
5000
        writeln!(writer, ";")?;
135
5000
        progress.print((v.value() + 1, game.num_of_vertices()));
136
    }
137

            
138
100
    Ok(())
139
100
}
140

            
141
#[cfg(test)]
142
mod tests {
143
    use merc_utilities::random_test;
144

            
145
    use crate::random_parity_game;
146

            
147
    use super::Itertools;
148
    use super::PG;
149
    use super::read_pg;
150
    use super::write_pg;
151

            
152
    #[test]
153
    #[cfg_attr(miri, ignore)]
154
1
    fn test_read_pg() {
155
1
        let parity_game = read_pg(include_bytes!("../../../../examples/vpg/example.pg") as &[u8]).unwrap();
156
1
        assert_eq!(parity_game.num_of_vertices(), 3002);
157
1
        assert_eq!(parity_game.num_of_edges(), 3968);
158
1
    }
159

            
160
    #[test]
161
    #[cfg_attr(miri, ignore)] // Test is too slow under miri
162
1
    fn test_random_io_pg() {
163
100
        random_test(100, |rng| {
164
100
            let game = random_parity_game(rng, true, 50, 10, 5);
165

            
166
100
            let mut buffer = Vec::new();
167
100
            write_pg(&mut buffer, &game).unwrap();
168

            
169
100
            let read_game = read_pg(buffer.as_slice()).unwrap();
170
100
            assert_eq!(game.num_of_vertices(), read_game.num_of_vertices());
171
100
            assert_eq!(game.num_of_edges(), read_game.num_of_edges());
172

            
173
5000
            for vertex in game.iter_vertices() {
174
10981
                let original_successors: Vec<_> = game.outgoing_edges(vertex).map(|e| e.to()).collect();
175
10981
                let read_successors: Vec<_> = read_game.outgoing_edges(vertex).map(|e| e.to()).collect();
176
5000
                assert_eq!(original_successors.len(), read_successors.len());
177
5000
                assert_eq!(
178
5000
                    original_successors.into_iter().sorted().collect::<Vec<_>>(),
179
5000
                    read_successors.into_iter().sorted().collect::<Vec<_>>()
180
                );
181
            }
182
100
        });
183
1
    }
184
}