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
        let owner =
92
8002
            Player::try_from_index(vertex_owner).ok_or(IOError::InvalidLine("owner must be 0 (even) or 1 (odd)"))?;
93
8002
        builder.add_vertex(vertex, owner, Priority::new(vertex_priority));
94

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

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

            
112
101
    Ok(builder.finish(true, true))
113
101
}
114

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

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

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

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

            
140
100
    Ok(())
141
100
}
142

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

            
147
    use crate::random_parity_game;
148

            
149
    use super::Itertools;
150
    use super::PG;
151
    use super::read_pg;
152
    use super::write_pg;
153

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

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

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

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

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