1
use merc_utilities::MercError;
2

            
3
use crate::DependencyGraph;
4

            
5
/// Default implementation of reorder when `kahypar` feature is not enabled.
6
#[cfg(not(feature = "kahypar"))]
7
pub fn reorder(_graph: &DependencyGraph) -> Result<Vec<usize>, MercError> {
8
    Err("reordering requires the `kahypar` feature to be enabled".into())
9
}
10

            
11
#[cfg(feature = "kahypar")]
12
mod inner {
13
    use super::*;
14

            
15
    use log::trace;
16

            
17
    use mt_kahypar::Context;
18
    use mt_kahypar::Hypergraph;
19
    use mt_kahypar::Objective;
20
    use mt_kahypar::Preset;
21

            
22
    /// Computes a variable reordering for symbolic transition relations using the MINCE algorithm.
23
    ///
24
    /// # Details
25
    ///
26
    /// The algorithm is described in the following paper:
27
    ///
28
    /// > Fadi A. Aloul, Igor L. Markov, Karem A. Sakallah:. MINCE: A Static Global Variable-Ordering Heuristic for SAT Search and BDD Manipulation. J. Univers. Comput. Sci. 10(12): 1562-1596 (2004). [DOI](https://doi.org/10.3217/jucs-010-12-1562)
29

            
30
    #[cfg(feature = "kahypar")]
31
    pub fn reorder(graph: &DependencyGraph) -> Result<Vec<usize>, MercError> {
32
        trace!("Starting MINCE with {graph:?}");
33

            
34
        let context = Context::builder()
35
            .preset(Preset::HighestQuality)
36
            .epsilon(0.000001)
37
            .k(2)
38
            .objective(Objective::Cut)
39
            .build()?;
40

            
41
        let vertices = (0..graph.num_of_vertices()).collect::<Vec<usize>>();
42
        mince(&context, &vertices, graph)
43
    }
44

            
45
    /// The recursive MINCE algorithm to compute a partitioning of the given dependency graph.
46
    ///
47
    /// # Details
48
    ///
49
    /// The `vertices` are the indices of the subgraph that we are considering
50
    fn mince(context: &Context, vertices: &[usize], graph: &DependencyGraph) -> Result<Vec<usize>, MercError> {
51
        trace!("MINCE called with vertices: {:?}", vertices);
52

            
53
        let hypergraph = create_hypergraph(context, vertices, graph)?;
54

            
55
        let partition = hypergraph.partition()?;
56
        debug_assert_eq!(partition.num_blocks(), 2, "MINCE only supports bipartitioning");
57

            
58
        // Compute the two partitions
59
        let partition = partition.extract_partition();
60

            
61
        // We kept the indices of vertices in the hypergraph the same as `vertices`, so we can now
62
        // separate them according to the partitioning.
63
        let left_vertices: Vec<usize> = vertices
64
            .iter()
65
            .enumerate()
66
            .filter_map(|(i, &v)| if partition[i] == 0 { Some(v) } else { None })
67
            .collect();
68

            
69
        let right_vertices: Vec<usize> = vertices
70
            .iter()
71
            .enumerate()
72
            .filter_map(|(i, &v)| if partition[i] == 1 { Some(v) } else { None })
73
            .collect();
74

            
75
        if left_vertices.is_empty() || right_vertices.is_empty() {
76
            // Cannot partition further, return as is
77
            trace!("MINCE reached base case with vertices: {:?}", vertices);
78
            return Ok(vertices.to_vec());
79
        }
80

            
81
        let mut left = mince(context, &left_vertices, graph)?;
82
        let mut right = mince(context, &right_vertices, graph)?;
83
        left.append(&mut right);
84

            
85
        // Check that the result is a valid permutation
86
        if cfg!(debug_assertions) {
87
            let mut copy = left.clone();
88
            copy.sort();
89

            
90
            debug_assert_eq!(copy, vertices, "Resulting order is not a valid permutation");
91
        }
92

            
93
        Ok(left)
94
    }
95

            
96
    /// Constructs a hypergraph CSR from the given read/write matrix.
97
    fn create_hypergraph<'a>(
98
        context: &'a Context,
99
        vertices: &[usize],
100
        graph: &DependencyGraph,
101
    ) -> Result<Hypergraph<'a>, MercError> {
102
        let mut hyperedge_indices = Vec::with_capacity(graph.num_of_relations() + 1);
103
        let mut hyperedges = Vec::new();
104

            
105
        let mut offset = 0usize;
106

            
107
        // Make a hyperedge for every relation
108
        // Track unique edges as sorted lists of local vertex indices
109
        let mut seen_edges: Vec<Vec<usize>> = Vec::new();
110

            
111
        for relation in graph.relations() {
112
            // Collect only variables that are in `vertices`, and use their local indices
113
            let mut edge_vars: Vec<usize> = relation
114
                .read_vars()
115
                .chain(relation.write_vars())
116
                .filter_map(|j| vertices.iter().position(|i| *i == j))
117
                .collect();
118

            
119
            // Deduplicate within-edge vertices and normalize order
120
            edge_vars.sort_unstable();
121
            edge_vars.dedup();
122

            
123
            if edge_vars.len() <= 1 {
124
                // Ignore self-loops and empty edges
125
                continue;
126
            }
127

            
128
            // Ignore duplicated edges
129
            if seen_edges.iter().any(|e| e == &edge_vars) {
130
                continue;
131
            }
132
            seen_edges.push(edge_vars.clone());
133

            
134
            // Add the edge to the hypergraph
135
            hyperedge_indices.push(offset);
136
            for j in edge_vars {
137
                hyperedges.push(j);
138
                offset += 1;
139
            }
140
        }
141

            
142
        hyperedge_indices.push(offset);
143

            
144
        Ok(Hypergraph::from_adjacency(
145
            context,
146
            graph.num_of_vertices(),
147
            &hyperedge_indices,
148
            &hyperedges,
149
            None,
150
            None,
151
        )?)
152
    }
153
}
154

            
155
#[cfg(feature = "kahypar")]
156
pub use inner::reorder;