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_utilities::MercError;
21

            
22
use crate::IOError;
23
use crate::PG;
24
use crate::ParityGame;
25
use crate::Player;
26
use crate::Priority;
27
use crate::VariabilityParityGame;
28
use crate::VertexIndex;
29
use crate::minus;
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(|(amount, total): (usize, usize)| info!("Read {} vertices ({}%)...", amount, amount * 100 / total), 1);
92
1
    let mut vertex_count = 0;
93
61015
    while let Some(line) = lines.next() {
94
        // Parse the line: <index> <priority> <owner> <outgoing_vertex>, <outgoing_vertex>, ...;
95
61014
        let mut parts = line.split_whitespace();
96

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

            
114
61014
        owner[index] = vertex_owner;
115
61014
        priority[index] = Priority::new(vertex_priority);
116

            
117
        // Store the offset for the vertex
118
61014
        vertices.push(edges_configuration.len());
119

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

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

            
141
61014
        progress.print((vertex_count + 1, num_of_vertices));
142
61014
        vertex_count += 1;
143
    }
144

            
145
    // Add the sentinel state.
146
1
    vertices.push(edges_configuration.len());
147

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

            
156
/// Parses a configuration set from a string representation into a BDD function, but also creates the necessary variables.
157
/// based on the length of the configurations.
158
1
fn parse_configuration(manager: &BDDManagerRef, config: &str) -> Result<(Vec<BDDFunction>, BDDFunction), MercError> {
159
1
    if let Some(first_part) = config.split('+').next() {
160
1
        let variables = manager.with_manager_exclusive(|manager| {
161
1
            manager
162
1
                .add_vars(first_part.len() as u32)
163
5
                .map(|i| BDDFunction::var(manager, i))
164
1
                .collect::<Result<Vec<_>, _>>()
165
1
        })?;
166

            
167
1
        let configuration = parse_configuration_set(manager, &variables, config)?;
168
1
        return Ok((variables, configuration));
169
    };
170

            
171
    Err(MercError::from(IOError::InvalidHeader("Empty configuration string")))
172
1
}
173

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

            
193
129583
        for part in config.split('+') {
194
129583
            let mut conjunction = BDDFunction::t(manager);
195

            
196
647915
            for (i, c) in part.chars().enumerate() {
197
647915
                let var = &variables[i];
198
647915
                match c {
199
12004
                    '1' => conjunction = conjunction.and(var)?,
200
40
                    '0' => conjunction = minus(&conjunction, var)?,
201
635871
                    '-' => {} // don't care
202
                    _ => {
203
                        return Err(MercError::from(IOError::InvalidHeader(
204
                            "Invalid character in configuration",
205
                        )));
206
                    }
207
                }
208
            }
209

            
210
129583
            result = result.or(&conjunction)?;
211
        }
212

            
213
129583
        Ok(result)
214
129583
    })
215
129583
}
216

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

            
223
    writeln!(writer, "confs {};", FormatConfigSet(game.configuration()))?;
224
    writeln!(writer, "parity {};", game.num_of_vertices())?;
225

            
226
    let progress = TimeProgress::new(
227
        |(index, total): (usize, usize)| info!("Wrote {} vertices ({}%)...", index, index * 100 / total),
228
        1,
229
    );
230
    for v in game.iter_vertices() {
231
        let prio = game.priority(v);
232
        let owner = game.owner(v).to_index();
233

            
234
        write!(writer, "{} {} {} ", v.value(), prio.value(), owner)?;
235
        write!(
236
            writer,
237
            "{}",
238
            game.outgoing_conf_edges(v).format_with(",", |edge, fmt| {
239
                fmt(&format_args!("{}|{}", edge.to(), FormatConfigSet(edge.configuration())))
240
            })
241
        )?;
242

            
243
        writeln!(writer, ";")?;
244
        progress.print((v.value(), game.num_of_vertices()));
245
    }
246

            
247
    Ok(())
248
}
249

            
250
#[cfg(test)]
251
mod tests {
252
    use super::*;
253

            
254
    #[test]
255
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
256
1
    fn test_read_vpg() {
257
1
        let manager = oxidd::bdd::new_manager(2048, 1024, 8);
258

            
259
1
        let parity_game = read_vpg(
260
1
            &manager,
261
1
            include_bytes!("../../../../examples/vpg/example.vpg") as &[u8],
262
        )
263
1
        .unwrap();
264

            
265
1
        assert_eq!(parity_game.num_of_vertices(), 61014);
266
1
    }
267
}