1
//! To keep with the theory, we use capitalized variable names for sets of vertices.
2
//! Authors: Maurice Laveaux, Sjef van Loo, Erik de Vink and Tim A.C. Willemse
3
#![allow(nonstandard_style)]
4
#![allow(unused)]
5

            
6
use std::fmt;
7
use std::ops::Index;
8

            
9
use bitvec::bitvec;
10
use bitvec::order::Lsb0;
11
use bitvec::vec::BitVec;
12
use log::debug;
13
use log::info;
14
use log::trace;
15
use merc_symbolic::FormatConfig;
16
use oxidd::BooleanFunction;
17
use oxidd::Edge;
18
use oxidd::Function;
19
use oxidd::Manager;
20
use oxidd::ManagerRef;
21
use oxidd::bdd::BDDFunction;
22
use oxidd::bdd::BDDManagerRef;
23
use oxidd::util::AllocResult;
24
use oxidd::util::OptBool;
25
use oxidd_core::util::EdgeDropGuard;
26

            
27
use merc_symbolic::FormatConfigSet;
28
use merc_symbolic::minus;
29
use merc_symbolic::minus_edge;
30
use merc_utilities::MercError;
31
use merc_utilities::Timing;
32

            
33
use crate::PG;
34
use crate::Player;
35
use crate::Priority;
36
use crate::Projected;
37
use crate::Repeat;
38
use crate::Set;
39
use crate::Submap;
40
use crate::VariabilityParityGame;
41
use crate::VariabilityPredecessors;
42
use crate::VertexIndex;
43
use crate::combine;
44
use crate::compute_reachable;
45
use crate::project_variability_parity_games_iter;
46
use crate::solve_zielonka;
47
use crate::x_and_not_x;
48

            
49
/// Variant of the Zielonka algorithm to use.
50
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
52
pub enum ZielonkaVariant {
53
    /// Product-based Zielonka variant.
54
    Product,
55
    /// Standard family-based Zielonka algorithm.
56
    Family,
57
    /// Left-optimised family-based Zielonka variant.
58
    FamilyOptimisedLeft,
59
}
60

            
61
/// Solves the given variability parity game using the specified Zielonka algorithm variant.
62
300
pub fn solve_variability_zielonka(
63
300
    manager_ref: &BDDManagerRef,
64
300
    game: &VariabilityParityGame,
65
300
    variant: ZielonkaVariant,
66
300
    alternative_solving: bool,
67
300
) -> Result<[Submap; 2], MercError> {
68
300
    debug_assert!(
69
300
        game.is_total(manager_ref)?,
70
        "Zielonka solver requires a total parity game"
71
    );
72

            
73
300
    let mut zielonka = VariabilityZielonkaSolver::new(manager_ref, game, alternative_solving);
74

            
75
    // Determine the initial set of vertices V
76
300
    let V = Submap::new(
77
300
        manager_ref,
78
300
        if alternative_solving {
79
            manager_ref.with_manager_shared(|manager| BDDFunction::t(manager))
80
        } else {
81
300
            game.configuration().clone()
82
        },
83
300
        game.num_of_vertices(),
84
    );
85

            
86
300
    let full_V = V.clone();
87
300
    let (W0, W1) = match variant {
88
200
        ZielonkaVariant::Family => zielonka.solve_recursive(V, 0)?,
89
100
        ZielonkaVariant::FamilyOptimisedLeft => zielonka.zielonka_family_optimised(V, 0)?,
90
        ZielonkaVariant::Product => {
91
            panic!("Product-based Zielonka is implemented in solve_product_zielonka");
92
        }
93
    };
94

            
95
300
    debug!("recursive calls" = zielonka.recursive_calls; "Performed {} recursive calls", zielonka.recursive_calls);
96
300
    if cfg!(debug_assertions) {
97
300
        zielonka.check_partition(&W0, &W1, &full_V)?;
98
    }
99

            
100
300
    let (W0, W1) = if alternative_solving {
101
        // Intersect the results with the game's configuration
102
        let config = game.configuration();
103
        (
104
            W0.and_function(manager_ref, config)?,
105
            W1.and_function(manager_ref, config)?,
106
        )
107
    } else {
108
300
        (W0, W1)
109
    };
110

            
111
300
    Ok([W0, W1])
112
300
}
113

            
114
/// Solves the given variability parity game using the product-based Zielonka algorithm.
115
100
pub fn solve_variability_product_zielonka<'a>(
116
100
    vpg: &'a VariabilityParityGame,
117
100
    timing: &'a Timing,
118
100
) -> impl Iterator<Item = Result<(Vec<OptBool>, BDDFunction, [Set; 2]), MercError>> + 'a {
119
745
    project_variability_parity_games_iter(vpg, timing).map(|result| {
120
745
        match result {
121
745
            Ok((Projected { bits, bdd, game }, timing)) => {
122
745
                let (reachable_pg, projection) = timing.measure("reachable", || compute_reachable(&game));
123

            
124
745
                debug!("Solving projection on {}...", FormatConfig(&bits));
125

            
126
745
                let (pg_solution, _) = solve_zielonka(&reachable_pg);
127
745
                let mut new_solution = [
128
745
                    bitvec![usize, Lsb0; 0; vpg.num_of_vertices()],
129
745
                    bitvec![usize, Lsb0; 0; vpg.num_of_vertices()],
130
745
                ];
131
16390
                for v in game.iter_vertices() {
132
16390
                    if let Some(proj_v) = projection[*v] {
133
                        // Vertex is reachable in the projection, set its solution
134
2775
                        if pg_solution[0][proj_v] {
135
1209
                            new_solution[0].set(*v, true);
136
1566
                        }
137
2775
                        if pg_solution[1][proj_v] {
138
1566
                            new_solution[1].set(*v, true);
139
1566
                        }
140
13615
                    }
141
                }
142

            
143
745
                Ok((bits, bdd, new_solution))
144
            }
145
            Err(result) => Err(result),
146
        }
147
745
    })
148
100
}
149

            
150
/// Verifies that the solution obtained from the variability product-based Zielonka solver
151
/// is consistent with the solution of the variability parity game.
152
100
pub fn verify_variability_product_zielonka_solution(
153
100
    vpg: &VariabilityParityGame,
154
100
    solution: &[Submap; 2],
155
100
    timing: &Timing,
156
100
) -> Result<(), MercError> {
157
100
    info!("Verifying variability product-based Zielonka solution...");
158
745
    solve_variability_product_zielonka(vpg, timing).try_for_each(|res| {
159
745
        match res {
160
745
            Ok((bits, cube, pg_solution)) => {
161
16390
                for v in vpg.iter_vertices() {
162
16390
                    if pg_solution[0][*v] {
163
                        // Won by Even
164
1209
                        assert!(
165
1209
                            solution[0][v].and(&cube)?.satisfiable(),
166
                            "Projection {}, vertex {v} is won by even in the product, but not in the vpg",
167
                            FormatConfig(&bits)
168
                        );
169
15181
                    }
170

            
171
16390
                    if pg_solution[1][*v] {
172
                        // Won by Odd
173
1566
                        assert!(
174
1566
                            solution[1][v].and(&cube)?.satisfiable(),
175
                            "Projection {}, vertex {v} is won by odd in the product, but not in the vpg",
176
                            FormatConfig(&bits)
177
                        );
178
14824
                    }
179
                }
180

            
181
745
                Ok(())
182
            }
183
            Err(res) => Err(res),
184
        }
185
745
    })?;
186

            
187
100
    Ok(())
188
100
}
189

            
190
struct VariabilityZielonkaSolver<'a> {
191
    game: &'a VariabilityParityGame,
192

            
193
    manager_ref: &'a BDDManagerRef,
194

            
195
    /// Instead of solving the game only for the valid configurations, solve for
196
    /// all configurations and then restrict the result at the end.
197
    alternative_solving: bool,
198

            
199
    /// Reused temporary queue for attractor computation.
200
    temp_queue: Vec<VertexIndex>,
201

            
202
    /// Keep track of the vertices in the temp_queue above in the attractor computation.
203
    temp_vertices: BitVec<usize, Lsb0>,
204

            
205
    /// Stores the predecessors of the game.
206
    predecessors: VariabilityPredecessors,
207

            
208
    /// Temporary storage for vertices per priority.
209
    priority_vertices: Vec<Vec<VertexIndex>>,
210

            
211
    /// The BDD function representing the universe configuration.
212
    true_bdd: BDDFunction,
213

            
214
    /// The BDD function representing the empty configuration.
215
    false_bdd: BDDFunction,
216

            
217
    /// Keeps track of the total number of recursive calls.
218
    recursive_calls: usize,
219
}
220

            
221
impl<'a> VariabilityZielonkaSolver<'a> {
222
    /// Creates a new VariabilityZielonkaSolver for the given game.
223
300
    pub fn new(manager_ref: &'a BDDManagerRef, game: &'a VariabilityParityGame, alternative_solving: bool) -> Self {
224
        // Keep track of the vertices for each priority
225
300
        let mut priority_vertices = Vec::new();
226

            
227
6600
        for v in game.iter_vertices() {
228
6600
            let prio = game.priority(v);
229

            
230
7498
            while prio >= priority_vertices.len() {
231
898
                priority_vertices.push(Vec::new());
232
898
            }
233

            
234
6600
            priority_vertices[prio].push(v);
235
        }
236

            
237
300
        let true_bdd = manager_ref.with_manager_shared(|manager| BDDFunction::t(manager));
238
300
        let false_bdd = manager_ref.with_manager_shared(|manager| BDDFunction::f(manager));
239

            
240
300
        Self {
241
300
            game,
242
300
            manager_ref,
243
300
            temp_queue: Vec::new(),
244
300
            temp_vertices: BitVec::repeat(false, game.num_of_vertices()),
245
300
            predecessors: VariabilityPredecessors::new(manager_ref, game),
246
300
            priority_vertices,
247
300
            recursive_calls: 0,
248
300
            alternative_solving,
249
300
            true_bdd,
250
300
            false_bdd,
251
300
        }
252
300
    }
253

            
254
    /// Solves the variability parity game for the given set of vertices V.
255
2115
    fn solve_recursive(&mut self, gamma: Submap, depth: usize) -> Result<(Submap, Submap), MercError> {
256
2115
        self.recursive_calls += 1;
257

            
258
        // For debugging mostly
259
2115
        let indent = Repeat::new(" ", depth);
260
2115
        let gamma_copy = gamma.clone();
261

            
262
        // 1. if \gamma == \epsilon then
263
2115
        if gamma.is_empty() {
264
760
            return Ok((gamma.clone(), gamma));
265
1355
        }
266

            
267
        // 5. m := max { p(v) | v in V && \gamma(v) \neq \emptyset }
268
1355
        let (highest_prio, lowest_prio) = self.get_highest_lowest_prio(&gamma);
269

            
270
        // 6. x := m mod 2
271
1355
        let x = Player::from_priority(&highest_prio);
272
1355
        let not_x = x.opponent();
273

            
274
        // 7. \mu := lambda v in V. bigcup { \gamma(v) | p(v) = m }
275
1355
        let mut mu = Submap::new(self.manager_ref, self.false_bdd.clone(), self.game.num_of_vertices());
276

            
277
1355
        self.manager_ref
278
1355
            .with_manager_shared(|manager| -> Result<(), MercError> {
279
10308
                for v in &self.priority_vertices[*highest_prio] {
280
10308
                    mu.set(manager, *v, gamma[*v].clone());
281
10308
                }
282

            
283
1355
                Ok(())
284
1355
            })?;
285

            
286
1355
        debug!(
287
            "|gamma| = {}, m = {}, l = {}, x = {}, |mu| = {}",
288
            gamma.number_of_non_empty(),
289
            highest_prio,
290
            lowest_prio,
291
            x,
292
            mu.number_of_non_empty()
293
        );
294

            
295
1355
        trace!("{indent}Vertices in gamma: {:?}", gamma);
296
1355
        trace!("{indent}Vertices in mu: {:?}", mu);
297
1355
        let alpha = self.attractor(x, &gamma, mu)?;
298
1355
        trace!("{indent}Vertices in alpha: {:?}", alpha);
299

            
300
        // 9. (omega'_0, omega'_1) := solve(\gamma \ \alpha)
301
1355
        debug!(
302
            "{indent}zielonka_family(gamma \\ alpha), |alpha| = {}",
303
            alpha.number_of_non_empty()
304
        );
305
1355
        let (omega1_0, omega1_1) = self.solve_recursive(gamma.clone().minus(self.manager_ref, &alpha)?, depth + 1)?;
306

            
307
1355
        let (mut omega1_x, mut omega1_not_x) = x_and_not_x(omega1_0, omega1_1, x);
308
1355
        if omega1_not_x.is_empty() {
309
            // 11. omega_x := omega'_x \cup alpha
310
795
            omega1_x = omega1_x.or(self.manager_ref, &alpha)?;
311
            // 20. return (omega_0, omega_1)
312
795
            Ok(combine(omega1_x, omega1_not_x, x))
313
        } else {
314
            // 14. \beta := attr_notalpha(\omega'_notx)
315
560
            let beta = self.attractor(not_x, &gamma, omega1_not_x)?;
316
            // 15. (omega''_0, omega''_1) := solve(gamma \ beta)
317
560
            debug!(
318
                "{indent}solve_rec(gamma \\ beta), |beta| = {}",
319
                beta.number_of_non_empty()
320
            );
321
560
            trace!("{indent}Vertices in beta: {:?}", beta);
322

            
323
560
            let (mut omega2_0, mut omega2_1) =
324
560
                self.solve_recursive(gamma.minus(self.manager_ref, &beta)?, depth + 1)?;
325

            
326
            // 17. omega''_notx := omega''_notx \cup \beta
327
560
            let (omega2_x, mut omega2_not_x) = x_and_not_x(omega2_0, omega2_1, x);
328
560
            omega2_not_x = omega2_not_x.or(self.manager_ref, &beta)?;
329

            
330
            // 20. return (omega_0, omega_1)
331
560
            if cfg!(debug_assertions) {
332
560
                self.check_partition(&omega2_x, &omega2_not_x, &gamma_copy)?;
333
            }
334
560
            Ok(combine(omega2_x, omega2_not_x, x))
335
        }
336
2115
    }
337

            
338
    /// Left-optimised Zielonka solver that has improved theoretical complexity, but might be slower in practice.
339
1051
    fn zielonka_family_optimised(&mut self, gamma: Submap, depth: usize) -> Result<(Submap, Submap), MercError> {
340
1051
        self.recursive_calls += 1;
341
1051
        let indent = Repeat::new(" ", depth);
342
1051
        let gamma_copy = gamma.clone();
343

            
344
        // 1. if \gamma == \epsilon then
345
1051
        if gamma.is_empty() {
346
            // 2. return (\epsilon, \epsilon)
347
378
            return Ok((gamma.clone(), gamma));
348
673
        }
349

            
350
        // 5. m := max { p(v) | v in V && \gamma(v) \neq \emptyset }
351
673
        let (highest_prio, lowest_prio) = self.get_highest_lowest_prio(&gamma);
352

            
353
        // 6. x := m mod 2
354
673
        let x = Player::from_priority(&highest_prio);
355
673
        let not_x = x.opponent();
356

            
357
        // 7. C := { c in \bigC | exists v in V : p(v) = m && c in \gamma(v) }
358
        // 8. \mu := lambda v in V. bigcup { \gamma(v) | p(v) = m }
359
673
        let mut mu = Submap::new(self.manager_ref, self.false_bdd.clone(), self.game.num_of_vertices());
360

            
361
673
        let mut C = self.false_bdd.clone();
362

            
363
673
        self.manager_ref
364
673
            .with_manager_shared(|manager| -> Result<(), MercError> {
365
5101
                for v in &self.priority_vertices[*highest_prio] {
366
5101
                    mu.set(manager, *v, gamma[*v].clone());
367
5101
                    C = C.or(&gamma[*v])?;
368
                }
369

            
370
673
                Ok(())
371
673
            })?;
372

            
373
673
        debug!(
374
            "{indent}|gamma| = {}, m = {}, l = {}, x = {}, |mu| = {}",
375
            gamma.number_of_non_empty(),
376
            highest_prio,
377
            lowest_prio,
378
            x,
379
            mu.number_of_non_empty()
380
        );
381

            
382
        // 9. alpha := attr_x(\mu).
383
673
        trace!("{indent}gamma: {:?}", gamma);
384
673
        trace!("{indent}C: {}", FormatConfigSet(&C));
385
673
        let alpha = self.attractor(x, &gamma, mu)?;
386
673
        trace!("{indent}alpha: {:?}", alpha);
387

            
388
        // 10. (omega'_0, omega'_1) := solve(gamma \ alpha)
389
673
        debug!(
390
            "{indent}zielonka_family_opt(gamma \\ alpha) |alpha| = {}",
391
            alpha.number_of_non_empty()
392
        );
393
673
        let (omega1_0, omega1_1) =
394
673
            self.zielonka_family_optimised(gamma.clone().minus(self.manager_ref, &alpha)?, depth + 1)?;
395

            
396
        // omega_prime[not_x] restricted to (gamma \ C)
397
673
        let C_restricted = minus(
398
673
            &if !self.alternative_solving {
399
673
                self.true_bdd.clone()
400
            } else {
401
                self.game.configuration().clone()
402
            },
403
673
            &C,
404
        )?;
405

            
406
673
        let (mut omega1_x, omega1_not_x) = x_and_not_x(omega1_0, omega1_1, x);
407
673
        let omega1_not_x_restricted = omega1_not_x.clone().minus_function(self.manager_ref, &C_restricted)?;
408

            
409
        // 10.
410
673
        if omega1_not_x_restricted.is_empty() {
411
            // 11. omega'_x := omega'_x \cup A
412
395
            omega1_x = omega1_x.or(self.manager_ref, &alpha)?;
413
395
            if cfg!(debug_assertions) {
414
395
                self.check_partition(&omega1_x, &omega1_not_x, &gamma_copy)?;
415
            }
416

            
417
            // 22. return (omega_0, omega_1)
418
395
            Ok(combine(omega1_x, omega1_not_x, x))
419
        } else {
420
            // C' := { c in C | exists v: c in omega'_not_x(v) }
421
278
            let mut C1 = self.false_bdd.clone();
422
6116
            for (_v, func) in omega1_not_x.iter() {
423
6116
                C1 = C1.or(func)?;
424
            }
425
278
            C1 = C1.and(&C)?;
426

            
427
            // beta := attr_not_x(omega'_not_x | C')
428
278
            let C1_restricted = minus(
429
278
                &if self.alternative_solving {
430
                    self.true_bdd.clone()
431
                } else {
432
278
                    self.game.configuration().clone()
433
                },
434
278
                &C1,
435
            )?;
436

            
437
278
            let omega1_not_x_restricted1 = omega1_not_x.clone().minus_function(self.manager_ref, &C1_restricted)?;
438
278
            trace!("{indent}omega'_notx_restricted: {:?}", omega1_not_x_restricted1);
439
278
            let alpha1 = self.attractor(not_x, &gamma, omega1_not_x_restricted1)?;
440
278
            trace!("{indent}alpha': {:?}", alpha1);
441

            
442
            // Solve on (gamma | C') \ alpha'
443
278
            let gamma_restricted = gamma.minus_function(self.manager_ref, &C1_restricted)?;
444

            
445
278
            debug!("{indent}zielonka_family_opt((gamma | C') \\ alpha')");
446
278
            let (omega2_0, omega2_1) =
447
278
                self.zielonka_family_optimised(gamma_restricted.minus(self.manager_ref, &alpha1)?, depth + 1)?;
448

            
449
            // 18. omega'_x := omega'_x\C' cup alpha\C' cup omega''_x
450
            // 19. omega_not_x := omega'_not_x\C' cup omega''_x cup beta
451
278
            let (omega2_x, omega2_not_x) = x_and_not_x(omega2_0, omega2_1, x);
452
278
            let omega1_x_restricted = omega1_x.minus_function(self.manager_ref, &C1)?;
453
278
            let omega1_not_x_restricted = omega1_not_x.minus_function(self.manager_ref, &C1)?;
454

            
455
278
            let alpha_restricted = alpha.minus_function(self.manager_ref, &C1)?;
456
278
            let omega2_x_result = omega2_x.or(
457
278
                self.manager_ref,
458
278
                &omega1_x_restricted.or(self.manager_ref, &alpha_restricted)?,
459
            )?;
460
278
            let omega2_not_x_result = omega2_not_x
461
278
                .or(self.manager_ref, &omega1_not_x_restricted)?
462
278
                .or(self.manager_ref, &alpha1)?;
463

            
464
278
            debug!("{indent}return (omega''_0, omega''_1)");
465
278
            Ok(combine(omega2_x_result, omega2_not_x_result, x))
466
        }
467
1051
    }
468

            
469
    /// Computes the attractor for `player` to the set `A` within the set of vertices `gamma`.
470
    ///
471
    /// # Details
472
    ///
473
    /// The definition of the attractor is as follows:
474
    ///     Attrx,γ (β) = intersection { α ⊆ γ | ∀v ∈ V, c ∈ C: (c ∈ β(v) ⇒ c ∈ α(v)) ∧
475
    ///          (v ∈ Vx ∧ (∃w ∈ V : v c −→ γ w ∧ c ∈ α(w)) ⇒ c ∈ α(v)) ∧
476
    ///          (v ∈ V¯x ∧ (∀w ∈ V : v c −→ γ w ⇒ c ∈ α(w)) ⇒ c ∈ α(v)) }
477
    ///
478
    /// The relation to the implementation is not entirely straightforward. The player `x` is called alpha here, and A is the beta set.
479
2866
    fn attractor(&mut self, alpha: Player, gamma: &Submap, mut A: Submap) -> Result<Submap, MercError> {
480
        // 2. Queue Q := {v \in V | A(v) != \emptyset }
481
2866
        debug_assert!(
482
2866
            self.temp_queue.is_empty(),
483
            "temp_queue should be empty at the start of attractor computation"
484
        );
485

            
486
2866
        self.manager_ref.with_manager_shared(|manager| {
487
16065
            for v in A.iter_vertices(manager) {
488
16065
                self.temp_queue.push(v);
489
16065

            
490
16065
                // temp_vertices keeps track of which vertices are in the queue.
491
16065
                self.temp_vertices.set(*v, true);
492
16065
            }
493
2866
        });
494

            
495
        // 4. While Q not empty do
496
        // 5. w := Q.pop()
497
2866
        self.manager_ref
498
2866
            .with_manager_shared(|manager| -> Result<(), MercError> {
499
                // Used for satisfiability checks
500
2866
                let f_edge = EdgeDropGuard::new(manager, BDDFunction::f_edge(manager));
501

            
502
24605
                while let Some(w) = self.temp_queue.pop() {
503
21739
                    self.temp_vertices.set(*w, false);
504

            
505
                    // For every v \in Ew do
506
31734
                    for (v, edge_guard) in self.predecessors.predecessors(w) {
507
31734
                        let mut a = EdgeDropGuard::new(
508
31734
                            manager,
509
31734
                            BDDFunction::and_edge(
510
31734
                                manager,
511
31734
                                &EdgeDropGuard::new(
512
31734
                                    manager,
513
31734
                                    BDDFunction::and_edge(manager, gamma[v].as_edge(manager), A[w].as_edge(manager))?,
514
                                ),
515
31734
                                edge_guard.as_edge(manager),
516
                            )?,
517
                        );
518

            
519
31734
                        if *a != *f_edge {
520
                            // 7. if v in V_\alpha
521
25088
                            if self.game.owner(v) == alpha {
522
8381
                                // 8. a := gamma(v) \intersect \theta(v, w) \intersect A(w)
523
8381
                                // This assignment has already been computed above.
524
8381
                            } else {
525
                                // 10. a := gamma(v)
526
16707
                                a = EdgeDropGuard::new(manager, gamma[v].clone().into_edge(manager));
527
                                // 11. for w' \in vE such that gamma(v) && theta(v, w') && \gamma(w') != \emptyset do
528
25574
                                for edge_w1 in self.game.outgoing_conf_edges(v) {
529
25574
                                    let tmp = EdgeDropGuard::new(
530
25574
                                        manager,
531
25574
                                        BDDFunction::and_edge(
532
25574
                                            manager,
533
25574
                                            &EdgeDropGuard::new(
534
25574
                                                manager,
535
25574
                                                BDDFunction::and_edge(
536
25574
                                                    manager,
537
25574
                                                    gamma[v].as_edge(manager),
538
25574
                                                    edge_w1.configuration().as_edge(manager),
539
                                                )?,
540
                                            ),
541
25574
                                            gamma[edge_w1.to()].as_edge(manager),
542
                                        )?,
543
                                    );
544

            
545
25574
                                    if *tmp != *f_edge {
546
                                        // 12. a := a && ((C \ (theta(v, w') && \gamma(w'))) \cup A(w'))
547
24297
                                        let tmp = EdgeDropGuard::new(
548
24297
                                            manager,
549
24297
                                            BDDFunction::and_edge(
550
24297
                                                manager,
551
24297
                                                edge_w1.configuration().as_edge(manager),
552
24297
                                                gamma[edge_w1.to()].as_edge(manager),
553
                                            )?,
554
                                        );
555

            
556
24297
                                        a = EdgeDropGuard::new(
557
24297
                                            manager,
558
24297
                                            BDDFunction::and_edge(
559
24297
                                                manager,
560
24297
                                                &a,
561
24297
                                                &EdgeDropGuard::new(
562
24297
                                                    manager,
563
24297
                                                    BDDFunction::or_edge(
564
24297
                                                        manager,
565
24297
                                                        &EdgeDropGuard::new(
566
24297
                                                            manager,
567
24297
                                                            minus_edge(
568
24297
                                                                manager,
569
24297
                                                                if self.alternative_solving {
570
                                                                    self.true_bdd.as_edge(manager)
571
                                                                } else {
572
24297
                                                                    self.game.configuration().as_edge(manager)
573
                                                                },
574
24297
                                                                &tmp,
575
                                                            )?,
576
                                                        ),
577
24297
                                                        A[edge_w1.to()].as_edge(manager),
578
                                                    )?,
579
                                                ),
580
                                            )?,
581
                                        );
582
1277
                                    }
583
                                }
584
                            }
585

            
586
                            // 15. a \ A(v) != \emptyset
587
25088
                            if *EdgeDropGuard::new(manager, minus_edge(manager, &a, A[v].as_edge(manager))?) != *f_edge
588
                            {
589
                                // 16. A(v) := A(v) \cup a
590
5763
                                let was_empty = *A[v].as_edge(manager) == *f_edge;
591
5763
                                let update = BDDFunction::or_edge(manager, A[v].as_edge(manager), &a)?;
592
5763
                                let is_empty = update == *f_edge;
593

            
594
5763
                                A.set(manager, v, BDDFunction::from_edge(manager, update));
595

            
596
                                // 17. if v not in Q then Q.push(v)
597
5763
                                if !self.temp_vertices[*v] {
598
5674
                                    self.temp_queue.push(v);
599
5674
                                    self.temp_vertices.set(*v, true);
600
5674
                                }
601
19325
                            }
602
6646
                        }
603
                    }
604
                }
605

            
606
2866
                Ok(())
607
2866
            })?;
608

            
609
2866
        debug_assert!(
610
2866
            !self.temp_vertices.any(),
611
            "temp_vertices should be empty after attractor computation"
612
        );
613

            
614
2866
        Ok(A)
615
2866
    }
616

            
617
    /// Returns the highest and lowest priority in the given set of vertices V.
618
2028
    fn get_highest_lowest_prio(&self, V: &Submap) -> (Priority, Priority) {
619
2028
        let mut highest = usize::MIN;
620
2028
        let mut lowest = usize::MAX;
621

            
622
2028
        self.manager_ref.with_manager_shared(|manager| {
623
22746
            for v in V.iter_vertices(manager) {
624
22746
                let prio = self.game.priority(v);
625
22746
                highest = highest.max(*prio);
626
22746
                lowest = lowest.min(*prio);
627
22746
            }
628
2028
        });
629

            
630
2028
        (Priority::new(highest), Priority::new(lowest))
631
2028
    }
632

            
633
    /// Checks that the sets W0 and W1 form a  partition w.r.t the submap V, i.e., their union is V and their intersection is empty.
634
1255
    fn check_partition(&self, W0: &Submap, W1: &Submap, V: &Submap) -> Result<(), MercError> {
635
1255
        self.manager_ref
636
1255
            .with_manager_shared(|manager| -> Result<(), MercError> {
637
18569
                for v in V.iter_vertices(manager) {
638
18569
                    let tmp = W0[v].or(&W1[v])?;
639

            
640
                    // The union of both solutions should be the entire set of vertices.
641
18569
                    assert!(
642
18569
                        tmp == V[v],
643
                        "The union of both solutions should be the entire set of vertices, but vertex {v} is missing."
644
                    );
645

            
646
18569
                    assert!(
647
18569
                        !W0[v].and(&W1[v])?.satisfiable(),
648
                        "The intersection of both solutions should be empty, but vertex {v} has non-empty intersection."
649
                    );
650
                }
651

            
652
1255
                Ok(())
653
1255
            })?;
654

            
655
1255
        Ok(())
656
1255
    }
657
}
658

            
659
#[cfg(test)]
660
mod tests {
661
    use merc_io::DumpFiles;
662
    use merc_macros::merc_test;
663
    use merc_utilities::Timing;
664
    use oxidd::BooleanFunction;
665
    use oxidd::Manager;
666
    use oxidd::ManagerRef;
667
    use oxidd::bdd::BDDFunction;
668
    use oxidd::util::AllocResult;
669

            
670
    use merc_utilities::random_test;
671

            
672
    use crate::PG;
673
    use crate::Submap;
674
    use crate::VertexIndex;
675
    use crate::ZielonkaVariant;
676
    use crate::project_variability_parity_games_iter;
677
    use crate::random_variability_parity_game;
678
    use crate::solve_variability_product_zielonka;
679
    use crate::solve_variability_zielonka;
680
    use crate::solve_zielonka;
681
    use crate::verify_variability_product_zielonka_solution;
682
    use crate::write_vpg;
683

            
684
    #[merc_test]
685
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
686
    fn test_random_variability_parity_game_solve() {
687
100
        random_test(100, |rng| {
688
100
            let mut files = DumpFiles::new("test_random_variability_parity_game_solve");
689

            
690
100
            let manager_ref = oxidd::bdd::new_manager(2048, 1024, 1);
691
100
            let vpg = random_variability_parity_game(&manager_ref, rng, true, 20, 3, 3, 3).unwrap();
692

            
693
100
            files.dump("input.vpg", |w| write_vpg(w, &vpg)).unwrap();
694

            
695
100
            let solution = solve_variability_zielonka(&manager_ref, &vpg, ZielonkaVariant::Family, false).unwrap();
696
100
            verify_variability_product_zielonka_solution(&vpg, &solution, &Timing::new()).unwrap();
697
100
        })
698
    }
699

            
700
    #[merc_test]
701
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
702
    fn test_random_variability_parity_game_solve_optimised_left() {
703
100
        random_test(100, |rng| {
704
100
            let mut files = DumpFiles::new("test_random_variability_parity_game_solve_optimised_left");
705

            
706
100
            let manager_ref = oxidd::bdd::new_manager(2048, 1024, 1);
707
100
            let vpg = random_variability_parity_game(&manager_ref, rng, true, 20, 3, 3, 3).unwrap();
708

            
709
100
            files.dump("input.vpg", |w| write_vpg(w, &vpg)).unwrap();
710

            
711
100
            let solution =
712
100
                solve_variability_zielonka(&manager_ref, &vpg, ZielonkaVariant::FamilyOptimisedLeft, false).unwrap();
713
100
            let solution_expected =
714
100
                solve_variability_zielonka(&manager_ref, &vpg, ZielonkaVariant::Family, false).unwrap();
715

            
716
100
            debug_assert_eq!(solution[0], solution_expected[0]);
717
100
            debug_assert_eq!(solution[1], solution_expected[1]);
718
100
        })
719
    }
720
}