1
use oxidd::BooleanFunction;
2
use oxidd::Manager;
3
use oxidd::ManagerRef;
4
use oxidd::bdd::BDDFunction;
5
use oxidd::bdd::BDDManagerRef;
6
use rand::Rng;
7

            
8
use merc_symbolic::random_bdd;
9
use merc_utilities::MercError;
10

            
11
use crate::PG;
12
use crate::ParityGame;
13
use crate::Player;
14
use crate::Priority;
15
use crate::VariabilityParityGame;
16
use crate::VertexIndex;
17
use crate::make_vpg_total;
18

            
19
/// Creates a random parity game with the given number of vertices, priorities, and outdegree.
20
500
pub fn random_parity_game(
21
500
    rng: &mut impl Rng,
22
500
    make_total: bool,
23
500
    num_of_vertices: usize,
24
500
    num_of_priorities: usize,
25
500
    outdegree: usize,
26
500
) -> ParityGame {
27
500
    assert!(num_of_vertices > 0, "Parity game must have at least one vertex");
28
500
    assert!(num_of_priorities > 0, "Parity game must have at least one priority");
29

            
30
    // Randomly assign priorities to each vertex in range [0, num_of_priorities).
31
500
    let priority: Vec<Priority> = (0..num_of_vertices)
32
11000
        .map(|_| Priority::new(rng.random_range(0..num_of_priorities)))
33
500
        .collect();
34

            
35
    // Option 1: owner based on parity of priority; Option 2: random owner.
36
    // Mirror random_lts_monolithic style by using randomness.
37
500
    let owner: Vec<Player> = (0..num_of_vertices)
38
11000
        .map(|_| Player::from_index(rng.random_range(0..2)))
39
500
        .collect();
40

            
41
    // Build edges using a closure that can be iterated twice (as required by from_edges).
42
    // We generate a deterministic set by capturing a precomputed edge list.
43
500
    let mut edge_list: Vec<(VertexIndex, VertexIndex)> = Vec::with_capacity(num_of_vertices * outdegree);
44

            
45
11000
    for v in 0..num_of_vertices {
46
        // For each vertex, generate 0..outdegree outgoing edges.
47
15837
        for _ in 0..rng.random_range(0..outdegree) {
48
15837
            let to = rng.random_range(0..num_of_vertices);
49
15837
            edge_list.push((VertexIndex::new(v), VertexIndex::new(to)));
50
15837
        }
51
    }
52

            
53
    // Ensure at least the initial vertex exists.
54
500
    let initial_vertex = VertexIndex::new(0);
55

            
56
1000
    ParityGame::from_edges(initial_vertex, owner, priority, make_total, || {
57
1000
        edge_list.iter().cloned()
58
1000
    })
59
500
}
60

            
61
/// Creates a random parity game with the given number of vertices, priorities, and outdegree.
62
300
pub fn random_variability_parity_game(
63
300
    manager_ref: &BDDManagerRef,
64
300
    rng: &mut impl Rng,
65
300
    make_total: bool,
66
300
    num_of_vertices: usize,
67
300
    num_of_priorities: usize,
68
300
    outdegree: usize,
69
300
    number_of_variables: u32,
70
300
) -> Result<VariabilityParityGame, MercError> {
71
300
    let pg = random_parity_game(rng, make_total, num_of_vertices, num_of_priorities, outdegree);
72

            
73
    // Check for existing variables.
74
300
    if manager_ref.with_manager_shared(|manager| manager.num_vars()) != 0 {
75
        return Err("BDD manager must not contain any variables yet".into());
76
300
    }
77

            
78
    // Create random feature variables.
79
300
    let variables = manager_ref.with_manager_exclusive(|manager| -> Result<Vec<BDDFunction>, MercError> {
80
300
        Ok(manager
81
300
            .add_vars(number_of_variables)
82
900
            .map(|i| BDDFunction::var(manager, i))
83
300
            .collect::<Result<Vec<_>, _>>()?)
84
300
    })?;
85

            
86
    // Overall configuration is the conjunction of all features (i.e., all features enabled).
87
300
    let configuration = random_bdd(manager_ref, rng, &variables)?;
88

            
89
    // Create random edge configurations.
90
300
    let mut edges_configuration: Vec<BDDFunction> = Vec::with_capacity(pg.num_of_edges());
91
300
    for _ in 0..pg.num_of_edges() {
92
6290
        edges_configuration.push(random_bdd(manager_ref, rng, &variables)?);
93
    }
94

            
95
300
    let result = VariabilityParityGame::new(pg, configuration, variables, edges_configuration);
96

            
97
300
    if make_total {
98
200
        make_vpg_total(manager_ref, &result)
99
    } else {
100
100
        Ok(result)
101
    }
102
300
}
103

            
104
#[cfg(test)]
105
mod tests {
106
    use merc_utilities::random_test;
107

            
108
    use crate::PG;
109
    use crate::random_parity_game;
110
    use crate::random_variability_parity_game;
111

            
112
    #[test]
113
1
    fn test_random_parity_game() {
114
100
        random_test(100, |rng| {
115
100
            let pg = random_parity_game(rng, false, 10, 5, 3);
116
100
            assert_eq!(pg.num_of_vertices(), 10);
117
100
        })
118
1
    }
119

            
120
    #[test]
121
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
122
1
    fn test_random_variability_parity_game() {
123
100
        random_test(100, |rng| {
124
100
            let manager_ref = oxidd::bdd::new_manager(2048, 1024, 1);
125
100
            let vpg = random_variability_parity_game(&manager_ref, rng, false, 10, 5, 3, 3).unwrap();
126
100
            assert_eq!(vpg.num_of_vertices(), 10);
127
100
        })
128
1
    }
129
}