1
use std::collections::HashMap;
2
use std::fmt;
3
use std::ops::ControlFlow;
4

            
5
use log::debug;
6
use log::info;
7

            
8
use log::warn;
9
use merc_collections::IndexedSet;
10
use merc_collections::VecBag;
11
use merc_io::TimeProgress;
12
use merc_lts::LTS;
13
use merc_lts::LabelledTransitionSystem;
14
use merc_lts::StateIndex;
15
use merc_lts::Transition;
16
use merc_lts::TransitionLabel;
17
use merc_syntax::ActFrm;
18
use merc_syntax::ActFrmBinaryOp;
19
use merc_syntax::FixedPointOperator;
20
use merc_syntax::ModalityOperator;
21
use merc_syntax::MultiAction;
22
use merc_syntax::RegFrm;
23
use merc_syntax::StateFrm;
24
use merc_syntax::StateFrmOp;
25
use merc_syntax::StateVarDecl;
26
use merc_syntax::apply_statefrm;
27
use merc_syntax::visit_action_formula;
28
use merc_syntax::visit_regular_formula;
29
use merc_syntax::visit_statefrm;
30
use merc_utilities::MercError;
31

            
32
use crate::FreshStateVarGenerator;
33
use crate::ModalEquationSystem;
34
use crate::ParityGame;
35
use crate::Player;
36
use crate::Priority;
37
use crate::VertexIndex;
38
use crate::compute_reachable;
39

            
40
/// Translates a labelled transition system into a variability parity game.
41
1217
pub fn translate(lts: &LabelledTransitionSystem<String>, formula: &StateFrm) -> Result<ParityGame, MercError> {
42
    // Parses all labels into MultiAction once
43
1217
    let labels = lts
44
1217
        .labels()
45
1217
        .iter()
46
3667
        .map(|label| {
47
3667
            if label.is_tau_label() {
48
1217
                Ok(MultiAction::tau())
49
            } else {
50
2450
                MultiAction::parse(label)
51
            }
52
3667
        })
53
1217
        .collect::<Result<Vec<MultiAction>, MercError>>()?;
54

            
55
    // Warn about any labels that are used in the formula but do not correspond to any label in the LTS.
56
1217
    warn_unknown_action_labels(formula, &labels);
57

            
58
1217
    let mut identifier_generator = FreshStateVarGenerator::new(formula);
59
1217
    let formula = translate_regular_formulas(formula.clone(), &mut identifier_generator);
60
1217
    debug!("Translated regular formulas: {}", formula);
61

            
62
1217
    let equation_system = ModalEquationSystem::new(&formula);
63
1217
    debug!("{}", equation_system);
64

            
65
1217
    let mut algorithm: Translation<'_, _, ()> = Translation::new(lts, &labels, &equation_system);
66
1217
    algorithm.translate(lts.initial_state_index(), 0, |_| (), |_, _| Ok(()))?;
67

            
68
    // Construct the parity game from the collected vertices and edges, where the `()` edge label is ignored.
69
1217
    let vertices = algorithm.vertices();
70
1217
    let result = ParityGame::from_edges(
71
1217
        VertexIndex::new(0),
72
1217
        vertices.iter().map(|(player, _)| *player).collect(),
73
1217
        vertices.iter().map(|(_, priority)| *priority).collect(),
74
        true,
75
63864
        || algorithm.edges.iter().map(|(s, _, t)| (*s, *t)),
76
    );
77

            
78
    // Check that all vertices are reachable from the initial vertex. After
79
    // totality it could be that the true or false nodes are not reachable.
80
1217
    if cfg!(debug_assertions) {
81
1217
        let (_, reachable_vertices) = compute_reachable(&result);
82
1217
        debug_assert!(
83
32627
            reachable_vertices.iter().all(|v| v.is_some()),
84
            "Not all vertices are reachable from the initial vertex"
85
        );
86
    }
87

            
88
1217
    Ok(result)
89
1217
}
90

            
91
/// Produces a warning for each label that is used in the formula but does not correspond to any label in the LTS.
92
1218
pub fn warn_unknown_action_labels(formula: &StateFrm, labels: &[MultiAction]) {
93
7813
    visit_statefrm::<(), _>(formula, |statefrm| {
94
7813
        if let StateFrm::Modality { formula, .. } = statefrm {
95
7877
            visit_regular_formula::<(), _>(formula, |regfrm| {
96
7877
                if let RegFrm::Action(act_frm) = regfrm {
97
5202
                    visit_action_formula::<(), _>(act_frm, |act_frm| {
98
5202
                        if let ActFrm::MultAct(action) = act_frm
99
5201
                            && !labels.contains(action)
100
                        {
101
                            warn!(
102
                                "Label {} in formula does not correspond to any label in the LTS",
103
                                action
104
                            );
105
5202
                        }
106

            
107
5202
                        Ok(ControlFlow::Continue(()))
108
5202
                    })?;
109
2676
                }
110

            
111
7877
                Ok(ControlFlow::Continue(()))
112
7877
            })?;
113
2612
        }
114

            
115
7813
        Ok(ControlFlow::Continue(()))
116
7813
    })
117
1218
    .expect("Failed to visit state formula");
118
1218
}
119

            
120
/// Translates regular formulas in modalities to fixpoint equations.
121
///
122
/// # Details
123
///
124
/// Applies these transformations:
125
///
126
/// ```plain
127
/// [a*]phi = nu I. [a]I && phi
128
/// [a+]phi = [a](nu I. [a]I && phi)
129
/// [a+b]phi = [a]phi || [b]phi
130
/// [a.b]phi = [a][b]phi
131
/// ```
132
///
133
/// and symmetrical for the diamond operator:
134
/// ```plain
135
/// <a*>phi = mu I. <a>I || phi
136
/// <a+>phi = <a>(mu I. <a>I || phi)
137
/// ```
138
3893
pub fn translate_regular_formulas(formula: StateFrm, identifier_generator: &mut FreshStateVarGenerator) -> StateFrm {
139
18507
    apply_statefrm(formula, |subformula| {
140
        if let StateFrm::Modality {
141
7874
            operator,
142
7874
            formula,
143
7874
            expr,
144
18507
        } = subformula
145
        {
146
7874
            return match formula {
147
5198
                merc_syntax::RegFrm::Action(_action_frm) => Ok(None),
148
2676
                merc_syntax::RegFrm::Iteration(reg_frm) => {
149
                    // Generate the I equation and replace the regular formula with it.
150
2676
                    let iteration_var = identifier_generator.generate("I");
151
2676
                    Ok(Some(translate_regular_formulas(
152
2676
                        convert_regular_iteration(*operator, reg_frm, iteration_var, operator, expr),
153
2676
                        identifier_generator,
154
2676
                    )))
155
                }
156
                merc_syntax::RegFrm::Plus(reg_frm) => {
157
                    // Generate the I equation and replace the regular formula with it.
158
                    let iteration_var = identifier_generator.generate("I");
159
                    Ok(Some(StateFrm::Modality {
160
                        operator: *operator,
161
                        formula: *reg_frm.clone(),
162
                        expr: Box::new(translate_regular_formulas(
163
                            convert_regular_iteration(*operator, reg_frm, iteration_var, operator, expr),
164
                            identifier_generator,
165
                        )),
166
                    }))
167
                }
168
                merc_syntax::RegFrm::Sequence { lhs, rhs } => Ok(Some(translate_regular_formulas(
169
                    StateFrm::Modality {
170
                        operator: *operator,
171
                        formula: *lhs.clone(),
172
                        expr: Box::new(StateFrm::Modality {
173
                            operator: *operator,
174
                            formula: *rhs.clone(),
175
                            expr: expr.clone(),
176
                        }),
177
                    },
178
                    identifier_generator,
179
                ))),
180
                merc_syntax::RegFrm::Choice { lhs, rhs } => Ok(Some(translate_regular_formulas(
181
                    StateFrm::Binary {
182
                        op: StateFrmOp::Disjunction,
183
                        lhs: Box::new(StateFrm::Modality {
184
                            operator: *operator,
185
                            formula: *lhs.clone(),
186
                            expr: expr.clone(),
187
                        }),
188
                        rhs: Box::new(StateFrm::Modality {
189
                            operator: *operator,
190
                            formula: *rhs.clone(),
191
                            expr: expr.clone(),
192
                        }),
193
                    },
194
                    identifier_generator,
195
                ))),
196
            };
197
10633
        }
198

            
199
10633
        Ok(None)
200
18507
    })
201
3893
    .expect("Failed to visit state formula")
202
3893
}
203

            
204
/// Convert an iteration regular formula to a fixpoint formula
205
///
206
/// # Details
207
///
208
/// The `modality` is the modality of the outer formula.
209
2676
fn convert_regular_iteration(
210
2676
    modality: ModalityOperator,
211
2676
    reg_frm: &RegFrm,
212
2676
    iteration_var: String,
213
2676
    operator: &ModalityOperator,
214
2676
    expr: &StateFrm,
215
2676
) -> StateFrm {
216
    StateFrm::FixedPoint {
217
2676
        operator: if modality == ModalityOperator::Box {
218
600
            FixedPointOperator::Greatest
219
        } else {
220
2076
            FixedPointOperator::Least
221
        },
222
2676
        variable: StateVarDecl::new(iteration_var.clone(), Vec::new()),
223
2676
        body: Box::new(StateFrm::Binary {
224
2676
            op: if modality == ModalityOperator::Box {
225
600
                StateFrmOp::Conjunction
226
            } else {
227
2076
                StateFrmOp::Disjunction
228
            },
229
2676
            lhs: Box::new(StateFrm::Modality {
230
2676
                operator: *operator,
231
2676
                formula: reg_frm.clone(),
232
2676
                expr: Box::new(StateFrm::Id(iteration_var, Vec::new())),
233
2676
            }),
234
2676
            rhs: Box::new(expr.clone()),
235
        }),
236
    }
237
2676
}
238

            
239
/// Is used to distinguish between StateFrm and Equation vertices in the vertex map.
240
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
241
enum Formula<'a> {
242
    StateFrm(&'a StateFrm),
243
    Equation(usize),
244
}
245

            
246
impl fmt::Display for Formula<'_> {
247
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248
        match self {
249
            Formula::StateFrm(statefrm) => write!(f, "{}", statefrm),
250
            Formula::Equation(i) => write!(f, "Equation({})", i),
251
        }
252
    }
253
}
254

            
255
/// Local struct to keep track of the translation state. The generic bound `E` is the
256
/// type of the edge labels.
257
///
258
/// Implements the translation from (s, Ψ) pairs to VPG vertices and edges.
259
/// However, to avoid the complication of merging sub-results we immediately
260
/// store the vertices and edges into mutable vectors. Furthermore, to avoid
261
/// stack overflows we use a breadth-first search approach with a queue. This
262
/// means that during queuing we immediately assign a fresh index to each (s, Ψ)
263
/// pair (if it does not yet exist) and then queue it to assign its actual
264
/// values later on.
265
pub(crate) struct Translation<'a, L, E> {
266
    vertex_map: IndexedSet<(StateIndex, Formula<'a>)>,
267
    vertices: Vec<Option<(Player, Priority)>>,
268
    edges: Vec<(VertexIndex, E, VertexIndex)>,
269

            
270
    /// Temporary storage used to check for duplicated outgoing edges.
271
    seen_modality_targets: HashMap<VertexIndex, usize>,
272

            
273
    /// Used for the breadth first search.
274
    queue: Vec<(StateIndex, Formula<'a>, VertexIndex)>,
275

            
276
    /// The parsed labels of the LTS.
277
    parsed_labels: &'a Vec<MultiAction>,
278

            
279
    /// The labelled transition system being translated.
280
    lts: &'a L,
281

            
282
    /// A reference to the modal equation system being translated.
283
    equation_system: &'a ModalEquationSystem,
284

            
285
    /// Use to print progress information.
286
    progress: TimeProgress<usize>,
287
}
288

            
289
impl<'a, L: LTS, E> Translation<'a, L, E> {
290
    /// Creates a new translation instance.
291
1218
    pub fn new(lts: &'a L, parsed_labels: &'a Vec<MultiAction>, equation_system: &'a ModalEquationSystem) -> Self {
292
1218
        let progress: TimeProgress<usize> = TimeProgress::new(
293
            |num_of_vertices: usize| {
294
                info!("Translated {} vertices...", num_of_vertices);
295
            },
296
            1,
297
        );
298

            
299
1218
        Self {
300
1218
            vertex_map: IndexedSet::new(),
301
1218
            vertices: Vec::new(),
302
1218
            edges: Vec::new(),
303
1218
            seen_modality_targets: HashMap::new(),
304
1218
            queue: Vec::new(),
305
1218
            lts,
306
1218
            parsed_labels,
307
1218
            equation_system,
308
1218
            progress,
309
1218
        }
310
1218
    }
311

            
312
    /// Perform the translation for the given `initial_state` and `initial_equation_index`.
313
    ///
314
    /// The `labelling` function is used to compute the edge label for the
315
    /// outgoing edges of this vertex. The argument is the transition
316
    /// corresponding to the modality, or None if there is no modality (e.g.,
317
    /// for conjunctions).
318
1218
    pub fn translate<F, C>(
319
1218
        &mut self,
320
1218
        initial_state: StateIndex,
321
1218
        initial_equation_index: usize,
322
1218
        labelling: F,
323
1218
        combine_labelling: C,
324
1218
    ) -> Result<(), MercError>
325
1218
    where
326
1218
        F: Fn(Option<Transition>) -> E,
327
1218
        C: Fn(&mut E, E) -> Result<(), MercError>,
328
    {
329
        // We store (state, formula, N) into the queue, where N is the vertex number assigned to this pair. This means
330
        // that during the traversal we can assume this N to exist.
331
1218
        self.queue = vec![(
332
1218
            initial_state,
333
1218
            Formula::Equation(initial_equation_index),
334
1218
            VertexIndex::new(0),
335
1218
        )];
336
1218
        self.vertex_map
337
1218
            .insert((initial_state, Formula::Equation(initial_equation_index)));
338
1218
        self.vertices.push(None); // Placeholder for the initial vertex
339

            
340
33868
        while let Some((s, formula, vertex_index)) = self.queue.pop() {
341
32650
            debug!("Translating vertex {}: (s={}, formula={})", vertex_index, s, formula);
342
32650
            self.progress.print(self.vertices.len());
343
32650
            match formula {
344
26338
                Formula::StateFrm(f) => {
345
26338
                    self.translate_vertex(s, f, vertex_index, &labelling, &combine_labelling)?;
346
                }
347
6312
                Formula::Equation(i) => {
348
6312
                    self.translate_equation(s, i, vertex_index, &labelling);
349
6312
                }
350
            }
351
        }
352

            
353
1218
        Ok(())
354
1218
    }
355

            
356
    /// Returns the collected vertices with placeholders removed.
357
1218
    pub fn vertices(&self) -> Vec<(Player, Priority)> {
358
1218
        debug_assert!(
359
32650
            self.vertices.iter().all(|v| v.is_some()),
360
            "All vertices should be assigned before retrieving the vertices"
361
        );
362

            
363
1218
        self.vertices
364
1218
            .iter()
365
32650
            .filter_map(|vertex| vertex.as_ref().copied())
366
1218
            .collect()
367
1218
    }
368

            
369
    /// Returns the collected edges, where the edge label is ignored.
370
2
    pub fn edges(&self) -> &Vec<(VertexIndex, E, VertexIndex)> {
371
2
        &self.edges
372
2
    }
373

            
374
    /// Translate a single vertex (s, Ψ) into the variability parity game vertex
375
    /// and its outgoing edges.
376
26338
    fn translate_vertex<F, C>(
377
26338
        &mut self,
378
26338
        s: StateIndex,
379
26338
        formula: &'a StateFrm,
380
26338
        vertex_index: VertexIndex,
381
26338
        labelling: &F,
382
26338
        combine_labelling: &C,
383
26338
    ) -> Result<(), MercError>
384
26338
    where
385
26338
        F: Fn(Option<Transition>) -> E,
386
26338
        C: Fn(&mut E, E) -> Result<(), MercError>,
387
    {
388
26338
        match formula {
389
1585
            StateFrm::True => {
390
1585
                // (s, true) → odd, 0
391
1585
                self.set_vertex(vertex_index, Player::Odd, Priority::new(0));
392
1585
            }
393
767
            StateFrm::False => {
394
767
                // (s, false) → even, 0
395
767
                self.set_vertex(vertex_index, Player::Even, Priority::new(0));
396
767
            }
397
7358
            StateFrm::Binary { op, lhs, rhs } => {
398
7358
                match op {
399
2910
                    StateFrmOp::Conjunction => {
400
2910
                        // (s, Ψ_1 ∧ Ψ_2) →_P odd, (s, Ψ_1) and (s, Ψ_2), 0
401
2910
                        self.set_vertex(vertex_index, Player::Odd, Priority::new(0));
402
2910
                        let s_psi_1 = self.queue_vertex(s, Formula::StateFrm(lhs));
403
2910
                        let s_psi_2 = self.queue_vertex(s, Formula::StateFrm(rhs));
404
2910

            
405
2910
                        self.edges.push((vertex_index, labelling(None), s_psi_1));
406
2910
                        self.edges.push((vertex_index, labelling(None), s_psi_2));
407
2910
                    }
408
4448
                    StateFrmOp::Disjunction => {
409
4448
                        // (s, Ψ_1 ∨ Ψ_2) →_P even, (s, Ψ_1) and (s, Ψ_2), 0
410
4448
                        self.set_vertex(vertex_index, Player::Even, Priority::new(0));
411
4448
                        let s_psi_1 = self.queue_vertex(s, Formula::StateFrm(lhs));
412
4448
                        let s_psi_2 = self.queue_vertex(s, Formula::StateFrm(rhs));
413
4448

            
414
4448
                        self.edges.push((vertex_index, labelling(None), s_psi_1));
415
4448
                        self.edges.push((vertex_index, labelling(None), s_psi_2));
416
4448
                    }
417
                    _ => {
418
                        unimplemented!("Cannot translate binary operator in {}", formula);
419
                    }
420
                }
421
            }
422
5097
            StateFrm::Id(identifier, _args) => {
423
5097
                let (i, _equation) = self
424
5097
                    .equation_system
425
5097
                    .find_equation_by_identifier(identifier)
426
5097
                    .expect("Variable must correspond to an equation");
427
5097

            
428
5097
                self.set_vertex(vertex_index, Player::Odd, Priority::new(0)); // The priority and owner do not matter here
429
5097
                let equation_vertex = self.queue_vertex(s, Formula::Equation(i));
430
5097
                self.edges.push((vertex_index, labelling(None), equation_vertex));
431
5097
            }
432
            StateFrm::Modality {
433
11531
                operator,
434
11531
                formula,
435
11531
                expr,
436
            } => {
437
11531
                self.translate_modality_vertex(
438
11531
                    s,
439
11531
                    *operator,
440
11531
                    formula,
441
11531
                    expr,
442
11531
                    vertex_index,
443
11531
                    labelling,
444
11531
                    combine_labelling,
445
                )?;
446
            }
447
            _ => {
448
                unimplemented!("Cannot translate formula {}", formula);
449
            }
450
        }
451

            
452
26338
        Ok(())
453
26338
    }
454

            
455
    /// Translates a modality vertex (s, [a]Ψ) or (s, <a>Ψ) into the variability parity game vertex and its outgoing edges.
456
    ///
457
    /// # Details
458
    ///
459
    /// Applies the following transformations:
460
    ///
461
    /// > (s, [a] Ψ) → odd, (s', Ψ) for all s' with s -a-> s', 0
462
    /// > (s, <a> Ψ) → even, (s', Ψ) for all s' with s -a-> s', 0
463
11531
    fn translate_modality_vertex<F, C>(
464
11531
        &mut self,
465
11531
        s: StateIndex,
466
11531
        operator: ModalityOperator,
467
11531
        formula: &'a RegFrm,
468
11531
        expr: &'a StateFrm,
469
11531
        vertex_index: VertexIndex,
470
11531
        labelling: &F,
471
11531
        combine_labelling: &C,
472
11531
    ) -> Result<(), MercError>
473
11531
    where
474
11531
        F: Fn(Option<Transition>) -> E,
475
11531
        C: Fn(&mut E, E) -> Result<(), MercError>,
476
    {
477
11531
        let player = match operator {
478
4252
            ModalityOperator::Box => Player::Odd,
479
7279
            ModalityOperator::Diamond => Player::Even,
480
        };
481

            
482
11531
        self.set_vertex(vertex_index, player, Priority::new(0));
483
11531
        self.seen_modality_targets.clear();
484

            
485
16530
        for transition in self.lts.outgoing_transitions(s) {
486
16530
            let action = &self.parsed_labels[*transition.label];
487

            
488
16530
            if match_regular_formula(formula, action) {
489
5831
                let s_prime_psi = self.queue_vertex(transition.to, Formula::StateFrm(expr));
490
5831
                let label = labelling(Some(transition));
491

            
492
5831
                if let Some(edge_index) = self.seen_modality_targets.get(&s_prime_psi).copied() {
493
                    combine_labelling(&mut self.edges[edge_index].1, label)?;
494
5831
                } else {
495
5831
                    let edge_index = self.edges.len();
496
5831
                    self.edges.push((vertex_index, label, s_prime_psi));
497
5831
                    self.seen_modality_targets.insert(s_prime_psi, edge_index);
498
5831
                }
499
10699
            }
500
        }
501

            
502
11531
        Ok(())
503
11531
    }
504

            
505
    /// Applies the translation to the given (s, equation) vertex.
506
6312
    fn translate_equation<F>(&mut self, s: StateIndex, equation_index: usize, vertex_index: VertexIndex, labelling: &F)
507
6312
    where
508
6312
        F: Fn(Option<Transition>) -> E,
509
    {
510
6312
        let equation = self.equation_system.equation(equation_index);
511
6312
        match equation.operator() {
512
4647
            FixedPointOperator::Least => {
513
4647
                // (s, μ X. Ψ) →_P odd, (s, Ψ[x := μ X. Ψ]), 2 * floor(AD(Ψ)/2) + 1. In Rust division is already floor.
514
4647
                self.set_vertex(
515
4647
                    vertex_index,
516
4647
                    Player::Odd,
517
4647
                    Priority::new(2 * (self.equation_system.alternation_depth(equation_index) / 2) + 1),
518
4647
                );
519
4647
                let s_psi = self.queue_vertex(s, Formula::StateFrm(equation.body()));
520
4647
                self.edges.push((vertex_index, labelling(None), s_psi));
521
4647
            }
522
1665
            FixedPointOperator::Greatest => {
523
1665
                // (s, ν X. Ψ) →_P even, (s, Ψ[x := ν X. Ψ]), 2 * floor(AD(Ψ)/2). In Rust division is already floor.
524
1665
                self.set_vertex(
525
1665
                    vertex_index,
526
1665
                    Player::Even,
527
1665
                    Priority::new(2 * (self.equation_system.alternation_depth(equation_index) / 2)),
528
1665
                );
529
1665
                let s_psi = self.queue_vertex(s, Formula::StateFrm(equation.body()));
530
1665
                self.edges.push((vertex_index, labelling(None), s_psi));
531
1665
            }
532
        }
533
6312
    }
534

            
535
    /// Assigns a concrete vertex value exactly once.
536
32650
    fn set_vertex(&mut self, vertex_index: VertexIndex, player: Player, priority: Priority) {
537
32650
        debug_assert!(
538
32650
            self.vertices[vertex_index].is_none(),
539
            "Vertex {vertex_index} was assigned more than once"
540
        );
541
32650
        self.vertices[vertex_index] = Some((player, priority));
542
32650
    }
543

            
544
    /// Queues a new pair to be translated, returning its vertex index.
545
31956
    fn queue_vertex(&mut self, s: StateIndex, formula: Formula<'a>) -> VertexIndex {
546
31956
        let (index, inserted) = self.vertex_map.insert((s, formula.clone()));
547
31956
        let vertex_index = VertexIndex::new(*index);
548

            
549
31956
        if inserted {
550
31432
            // New vertex, assign placeholder values
551
31432
            self.vertices.resize(*vertex_index + 1, None);
552
31432
            self.queue.push((s, formula, vertex_index));
553
31432
        }
554

            
555
31956
        vertex_index
556
31956
    }
557
}
558

            
559
/// Returns true iff the given action matches the regular formula.
560
16530
fn match_regular_formula(formula: &RegFrm, action: &MultiAction) -> bool {
561
16530
    match formula {
562
16530
        RegFrm::Action(action_formula) => match_action_formula(action_formula, action),
563
        RegFrm::Choice { lhs, rhs } => match_regular_formula(lhs, action) || match_regular_formula(rhs, action),
564
        _ => {
565
            unimplemented!("Cannot translate regular formula {}", formula);
566
        }
567
    }
568
16530
}
569

            
570
/// Returns true iff the given action matches the action formula.
571
16622
fn match_action_formula(formula: &ActFrm, action: &MultiAction) -> bool {
572
16622
    match formula {
573
        ActFrm::True => true,
574
        ActFrm::False => false,
575
16530
        ActFrm::MultAct(expected_action) => match_multi_action(expected_action, action),
576
        ActFrm::Binary { op, lhs, rhs } => match op {
577
            ActFrmBinaryOp::Union => match_action_formula(lhs, action) || match_action_formula(rhs, action),
578
            ActFrmBinaryOp::Intersect => match_action_formula(lhs, action) && match_action_formula(rhs, action),
579
            _ => {
580
                unimplemented!("Cannot translate binary operator {}", formula);
581
            }
582
        },
583
92
        ActFrm::Negation(expr) => !match_action_formula(expr, action),
584
        _ => {
585
            unimplemented!("Cannot translate action formula {}", formula);
586
        }
587
    }
588
16622
}
589

            
590
/// Returns true iff the two multi-actions denote the same multi-set of actions.
591
///
592
/// The order of actions within a multi-action is irrelevant, but the multiplicity of each action
593
/// must match exactly (e.g. `a | a | b` does not match `a | b | b`).
594
16530
fn match_multi_action(expected: &MultiAction, actual: &MultiAction) -> bool {
595
16530
    if expected.actions.len() != actual.actions.len() {
596
8628
        return false;
597
7902
    }
598

            
599
7902
    VecBag::from_vec(expected.actions.clone()) == VecBag::from_vec(actual.actions.clone())
600
16530
}
601

            
602
#[cfg(test)]
603
mod tests {
604
    use merc_lts::read_aut;
605
    use merc_macros::merc_test;
606
    use merc_syntax::UntypedStateFrmSpec;
607

            
608
    use crate::PG;
609
    use crate::compute_reachable;
610
    use crate::translate;
611

            
612
    #[merc_test]
613
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
614
    fn test_translate_abp_infinitely_often_receive_d1() {
615
        let lts = read_aut(include_bytes!("../../../examples/lts/abp.aut") as &[u8]).unwrap();
616
        let formula =
617
            UntypedStateFrmSpec::parse(include_str!("../../../examples/pbes/infinitely_often_receive_d1.mcf")).unwrap();
618

            
619
        let pg = translate(&lts, &formula.formula).unwrap();
620

            
621
        assert!(pg.is_total(), "The translated parity game should be total");
622
        assert!(
623
375
            compute_reachable(&pg).1.iter().all(|v| v.is_some()),
624
            "All vertices should be reachable from the initial vertex"
625
        );
626
    }
627
}