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_io::TimeProgress;
11
use merc_lts::LTS;
12
use merc_lts::LabelledTransitionSystem;
13
use merc_lts::StateIndex;
14
use merc_lts::Transition;
15
use merc_lts::TransitionLabel;
16
use merc_syntax::ActFrm;
17
use merc_syntax::ActFrmBinaryOp;
18
use merc_syntax::FixedPointOperator;
19
use merc_syntax::ModalityOperator;
20
use merc_syntax::MultiAction;
21
use merc_syntax::RegFrm;
22
use merc_syntax::StateFrm;
23
use merc_syntax::StateFrmOp;
24
use merc_syntax::StateVarDecl;
25
use merc_syntax::apply_statefrm;
26
use merc_syntax::visit_action_formula;
27
use merc_syntax::visit_regular_formula;
28
use merc_syntax::visit_statefrm;
29
use merc_utilities::MercError;
30

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

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

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

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

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

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

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

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

            
87
2361
    Ok(result)
88
2361
}
89

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

            
106
9674
                        Ok(ControlFlow::Continue(()))
107
9674
                    })?;
108
4992
                }
109

            
110
14665
                Ok(ControlFlow::Continue(()))
111
14665
            })?;
112
4712
        }
113

            
114
14385
        Ok(ControlFlow::Continue(()))
115
14385
    })
116
2362
    .expect("Failed to visit state formula");
117
2362
}
118

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

            
198
19681
        Ok(None)
199
34343
    })
200
7353
    .expect("Failed to visit state formula")
201
7353
}
202

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

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

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

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

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

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

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

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

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

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

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

            
302
2362
        Self {
303
2362
            vertex_map: IndexedSet::new(),
304
2362
            vertices: Vec::new(),
305
2362
            edges: Vec::new(),
306
2362
            seen_modality_targets: HashMap::new(),
307
2362
            queue: Vec::new(),
308
2362
            lts,
309
2362
            parsed_labels,
310
2362
            equation_system,
311
2362
            progress,
312
2362
        }
313
2362
    }
314

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

            
343
58828
        while let Some((s, formula, vertex_index)) = self.queue.pop() {
344
56466
            debug!("Translating vertex {}: (s={}, formula={})", vertex_index, s, formula);
345
56466
            self.progress.print(self.vertices.len());
346
56466
            match formula {
347
45539
                Formula::StateFrm(f) => {
348
45539
                    self.translate_vertex(s, f, vertex_index, &labelling, &combine_labelling)?;
349
                }
350
10927
                Formula::Equation(i) => {
351
10927
                    self.translate_equation(s, i, vertex_index, &labelling);
352
10927
                }
353
            }
354
        }
355

            
356
2362
        Ok(())
357
2362
    }
358

            
359
    /// Returns the collected vertices with placeholders removed.
360
2362
    pub(crate) fn vertices(&self) -> Vec<(Player, Priority)> {
361
2362
        debug_assert!(
362
56466
            self.vertices.iter().all(|v| v.is_some()),
363
            "All vertices should be assigned before retrieving the vertices"
364
        );
365

            
366
2362
        self.vertices
367
2362
            .iter()
368
56466
            .filter_map(|vertex| vertex.as_ref().copied())
369
2362
            .collect()
370
2362
    }
371

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

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

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

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

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

            
455
45539
        Ok(())
456
45539
    }
457

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

            
486
20045
        self.set_vertex(vertex_index, player, Priority::new(0));
487
20045
        self.seen_modality_targets.clear();
488

            
489
28746
        for transition in self.lts.outgoing_transitions(s) {
490
28746
            let action = &self.parsed_labels[*transition.label];
491

            
492
28746
            if match_regular_formula(formula, action) {
493
10100
                let s_prime_psi = self.queue_vertex(transition.to, Formula::StateFrm(expr));
494
10100
                let label = labelling(Some(transition));
495

            
496
10100
                if let Some(edge_index) = self.seen_modality_targets.get(&s_prime_psi).copied() {
497
                    combine_labelling(&mut self.edges[edge_index].1, label)?;
498
10100
                } else {
499
10100
                    let edge_index = self.edges.len();
500
10100
                    self.edges.push((vertex_index, label, s_prime_psi));
501
10100
                    self.seen_modality_targets.insert(s_prime_psi, edge_index);
502
10100
                }
503
18646
            }
504
        }
505

            
506
20045
        Ok(())
507
20045
    }
508

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

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

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

            
553
54955
        if inserted {
554
54104
            // New vertex, assign placeholder values
555
54104
            self.vertices.resize(*vertex_index + 1, None);
556
54104
            self.queue.push((s, formula, vertex_index));
557
54104
        }
558

            
559
54955
        vertex_index
560
54955
    }
561
}
562

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

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

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

            
603
    // Compare as multisets without cloning the action data (notably the action
604
    // identifiers): sort references and compare element-wise.
605
13584
    let mut expected_actions: Vec<_> = expected.actions.iter().collect();
606
13584
    let mut actual_actions: Vec<_> = actual.actions.iter().collect();
607
13584
    expected_actions.sort_unstable();
608
13584
    actual_actions.sort_unstable();
609
13584
    expected_actions == actual_actions
610
28746
}
611

            
612
#[cfg(test)]
613
mod tests {
614
    use merc_lts::read_aut;
615
    use merc_macros::merc_test;
616
    use merc_syntax::UntypedStateFrmSpec;
617

            
618
    use crate::PG;
619
    use crate::compute_reachable;
620
    use crate::translate;
621

            
622
    #[merc_test]
623
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
624
    fn test_translate_abp_infinitely_often_receive_d1() {
625
        let lts = read_aut(include_bytes!("../../../examples/lts/abp.aut") as &[u8]).unwrap();
626
        let formula =
627
            UntypedStateFrmSpec::parse(include_str!("../../../examples/pbes/infinitely_often_receive_d1.mcf")).unwrap();
628

            
629
        let pg = translate(&lts, &formula.formula).unwrap();
630

            
631
        assert!(pg.is_total(), "The translated parity game should be total");
632
        assert!(
633
375
            compute_reachable(&pg).1.iter().all(|v| v.is_some()),
634
            "All vertices should be reachable from the initial vertex"
635
        );
636
    }
637
}