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 oxidd::BooleanFunction;
10
use oxidd::Manager;
11
use oxidd::ManagerRef;
12
use oxidd::bdd::BDDFunction;
13
use oxidd::bdd::BDDManagerRef;
14
use regex::Regex;
15
use streaming_iterator::StreamingIterator;
16

            
17
use merc_io::LineIterator;
18
use merc_io::TimeProgress;
19
use merc_symbolic::FormatConfigSet;
20
use merc_symbolic::minus;
21
use merc_utilities::MercError;
22

            
23
use crate::IOError;
24
use crate::PG;
25
use crate::ParityGame;
26
use crate::Player;
27
use crate::Priority;
28
use crate::VariabilityParityGame;
29
use crate::VertexIndex;
30

            
31
/// Reads a variability parity game in an extended PGSolver `.vpg` format from the given reader.
32
/// Note that the reader is buffered internally using a `BufReader`.
33
///
34
/// # Details
35
///
36
/// The format starts with a header, followed by the vertices
37
///
38
/// parity <num_of_vertices>;
39
/// `<index> <priority> <owner> <outgoing_vertex>,<outgoing_vertex>,...;`
40
/// Each outgoing edge is represented as `<to>|<configuration_set>`. For the
41
/// format of the configuration set see [parse_configuration_set]
42
1
pub fn read_vpg(manager: &BDDManagerRef, reader: impl Read) -> Result<VariabilityParityGame, MercError> {
43
1
    info!("Reading variability parity game in .vpg format...");
44

            
45
1
    manager.with_manager_exclusive(|manager| {
46
1
        debug_assert_eq!(
47
1
            manager.num_vars(),
48
            0,
49
            "A BDD manager can only hold the variables for a single variability parity game"
50
        )
51
1
    });
52

            
53
1
    let mut lines = LineIterator::new(reader);
54
1
    lines.advance();
55
1
    let header = lines
56
1
        .get()
57
1
        .ok_or(IOError::InvalidHeader("The first line should be the confs header"))?;
58

            
59
    // Read the confs <configurations> line
60
1
    let confs_regex = Regex::new(r#"confs\s+([+-01]*)\s*;"#).expect("Regex compilation should not fail");
61
1
    let (_, [configurations_txt]) = confs_regex
62
1
        .captures(header)
63
1
        .ok_or(IOError::InvalidHeader("header does not match confs <configurations>;"))?
64
1
        .extract();
65
1
    let (variables, configurations) = parse_configuration(manager, configurations_txt)?;
66

            
67
    // Read the parity header
68
1
    let header_regex = Regex::new(r#"parity\s+([0-9]+)\s*;"#).expect("Regex compilation should not fail");
69
1
    let header = lines
70
1
        .next()
71
1
        .ok_or(IOError::InvalidHeader("The second line should be the parity header"))?;
72

            
73
1
    let (_, [num_of_vertices_txt]) = header_regex
74
1
        .captures(header)
75
1
        .ok_or(IOError::InvalidHeader(
76
1
            "header does not match parity <num_of_vertices>;",
77
1
        ))?
78
1
        .extract();
79

            
80
1
    let num_of_vertices: usize = num_of_vertices_txt.parse()?;
81

            
82
    // Collect that data into the parity game structure
83
1
    let mut owner: Vec<Player> = vec![Player::Even; num_of_vertices];
84
1
    let mut priority: Vec<Priority> = vec![Priority::new(0); num_of_vertices];
85

            
86
1
    let mut vertices: Vec<usize> = Vec::with_capacity(num_of_vertices + 1);
87
1
    let mut edges_to: Vec<VertexIndex> = Vec::with_capacity(num_of_vertices);
88
1
    let mut edges_configuration: Vec<BDDFunction> = Vec::with_capacity(num_of_vertices);
89

            
90
    // Print progress messages
91
1
    let progress = TimeProgress::new(
92
        |(amount, total): (usize, usize)| info!("Read {} vertices ({}%)...", amount, amount * 100 / total),
93
        1,
94
    );
95
1
    let mut vertex_count = 0;
96
3003
    while let Some(line) = lines.next() {
97
        // Parse the line: <index> <priority> <owner> <outgoing_vertex>, <outgoing_vertex>, ...;
98
3002
        let mut parts = line.split_whitespace();
99

            
100
3002
        let index: usize = parts
101
3002
            .next()
102
3002
            .ok_or(IOError::InvalidLine("Expected at least <index> ...;"))?
103
3002
            .parse()?;
104
3002
        let vertex_priority: usize = parts
105
3002
            .next()
106
3002
            .ok_or(IOError::InvalidLine("Expected at least <index> <priority> ...;"))?
107
3002
            .parse()?;
108
3002
        let vertex_owner = Player::from_index(
109
3002
            parts
110
3002
                .next()
111
3002
                .ok_or(IOError::InvalidLine(
112
3002
                    "Expected at least <index> <priority> <owner> ...;",
113
3002
                ))?
114
3002
                .parse()?,
115
        );
116

            
117
3002
        owner[index] = vertex_owner;
118
3002
        priority[index] = Priority::new(vertex_priority);
119

            
120
        // Store the offset for the vertex
121
3002
        vertices.push(edges_configuration.len());
122

            
123
3002
        for successors in parts {
124
            // Parse successors (remaining parts, removing trailing semicolon)
125
4409
            for successor in successors
126
3002
                .trim_end_matches(';')
127
3002
                .split(',')
128
4409
                .filter(|s| !s.trim().is_empty())
129
            {
130
4409
                let parts: Vec<&str> = successor.trim().split('|').collect();
131
4409
                let successor_index: usize = parts[0].trim().parse()?;
132
4409
                edges_to.push(VertexIndex::new(successor_index));
133

            
134
4409
                if parts.len() > 1 {
135
4409
                    let config = parse_configuration_set(manager, &variables, parts[1].trim())?;
136
4409
                    edges_configuration.push(config);
137
                } else {
138
                    // No configuration specified, use true (all configurations)
139
                    edges_configuration.push(manager.with_manager_shared(|m| BDDFunction::t(m)));
140
                }
141
            }
142
        }
143

            
144
3002
        progress.print((vertex_count + 1, num_of_vertices));
145
3002
        vertex_count += 1;
146
    }
147

            
148
    // Add the sentinel state.
149
1
    vertices.push(edges_configuration.len());
150

            
151
1
    Ok(VariabilityParityGame::new(
152
1
        ParityGame::new(VertexIndex::new(0), owner, priority, vertices, edges_to),
153
1
        configurations,
154
1
        variables,
155
1
        edges_configuration,
156
1
    ))
157
1
}
158

            
159
/// Parses a configuration set from a string representation into a BDD function, but also creates the necessary variables.
160
/// based on the length of the configurations.
161
1
fn parse_configuration(manager_ref: &BDDManagerRef, config: &str) -> Result<(Vec<BDDFunction>, BDDFunction), MercError> {
162
    
163
    // Check for existing variables
164
1
    if manager_ref.with_manager_shared(|manager| manager.num_vars()) != 0 {
165
        return Err("BDD manager must not contain any variables yet".into());
166
1
    }
167

            
168
1
    if let Some(first_part) = config.split('+').next() {
169
1
        let variables = manager_ref.with_manager_exclusive(|manager| {
170
1
            manager
171
1
                .add_vars(first_part.len() as u32)
172
10
                .map(|i| BDDFunction::var(manager, i))
173
1
                .collect::<Result<Vec<_>, _>>()
174
1
        })?;
175

            
176
1
        let configuration = parse_configuration_set(manager_ref, &variables, config)?;
177
1
        return Ok((variables.to_vec(), configuration));
178
    };
179

            
180
    Err(MercError::from(IOError::InvalidHeader("Empty configuration string")))
181
1
}
182

            
183
/// Parses a configuration from a string representation into a BDD function.
184
///
185
/// # Details
186
///
187
/// A configuration is represented as a string \<entry\>+\<entry\>+..., where each entry is either
188
/// a sequence consisting of '-', '0', and '1', representing don't care, false, and true respectively.
189
/// The length of the sequence determines the number of boolean variables. So `-1--` represents a boolean
190
/// function over 4 variables.
191
///
192
/// The variables must be defined beforehand and are assumed to be in order, i.e., the first character
193
/// corresponds to variable 0, the second to variable 1, and so on.
194
4410
pub fn parse_configuration_set(
195
4410
    manager_ref: &BDDManagerRef,
196
4410
    variables: &[BDDFunction],
197
4410
    config: &str,
198
4410
) -> Result<BDDFunction, MercError> {
199
4410
    manager_ref.with_manager_shared(|manager| -> Result<BDDFunction, MercError> {
200
4410
        let mut result = BDDFunction::f(manager);
201

            
202
4418
        for part in config.split('+') {
203
4418
            let mut conjunction = BDDFunction::t(manager);
204

            
205
44180
            for (i, c) in part.chars().enumerate() {
206
44180
                let var = &variables[i];
207
44180
                match c {
208
474
                    '1' => conjunction = conjunction.and(var)?,
209
207
                    '0' => conjunction = minus(&conjunction, var)?,
210
43499
                    '-' => {} // don't care
211
                    _ => {
212
                        return Err(MercError::from(IOError::InvalidHeader(
213
                            "Invalid character in configuration",
214
                        )));
215
                    }
216
                }
217
            }
218

            
219
4418
            result = result.or(&conjunction)?;
220
        }
221

            
222
4410
        Ok(result)
223
4410
    })
224
4410
}
225

            
226
/// Writes the given parity game to the given writer in .vpg format.
227
/// Note that the writer is buffered internally using a `BufWriter`.
228
pub fn write_vpg(writer: &mut impl Write, game: &VariabilityParityGame) -> Result<(), MercError> {
229
    info!("Writing variability parity game to .vpg format...");
230
    let mut writer = BufWriter::new(writer);
231

            
232
    writeln!(writer, "confs {};", FormatConfigSet(game.configuration()))?;
233
    writeln!(writer, "parity {};", game.num_of_vertices())?;
234

            
235
    let progress = TimeProgress::new(
236
        |(index, total): (usize, usize)| info!("Wrote {} vertices ({}%)...", index, index * 100 / total),
237
        1,
238
    );
239
    for v in game.iter_vertices() {
240
        let prio = game.priority(v);
241
        let owner = game.owner(v).to_index();
242

            
243
        write!(writer, "{} {} {} ", v.value(), prio.value(), owner)?;
244
        write!(
245
            writer,
246
            "{}",
247
            game.outgoing_conf_edges(v).format_with(",", |edge, fmt| {
248
                fmt(&format_args!("{}|{}", edge.to(), FormatConfigSet(edge.configuration())))
249
            })
250
        )?;
251

            
252
        writeln!(writer, ";")?;
253
        progress.print((v.value(), game.num_of_vertices()));
254
    }
255

            
256
    Ok(())
257
}
258

            
259
#[cfg(test)]
260
mod tests {
261
    use super::*;
262

            
263
    #[test]
264
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
265
1
    fn test_read_vpg() {
266
1
        let manager = oxidd::bdd::new_manager(2048, 1024, 8);
267

            
268
1
        let parity_game = read_vpg(
269
1
            &manager,
270
1
            include_bytes!("../../../../examples/vpg/example.vpg") as &[u8],
271
        )
272
1
        .unwrap();
273

            
274
1
        assert_eq!(parity_game.num_of_vertices(), 3002);
275
1
        assert_eq!(parity_game.num_of_edges(), 4409);
276
1
    }
277
}