// see if we get better performance with different integer types type Order = bitvec::order::Lsb0; type Element = usize; type BitVec = bitvec::vec::BitVec; type BitSlice = bitvec::prelude::BitSlice; /// A square boolean matrix used to store relations /// /// We use this for sorting definitions so every definition is defined before it is used. /// This functionality is also used to spot and report invalid recursion. #[derive(Debug)] pub(crate) struct ReferenceMatrix { bitvec: BitVec, length: usize, } impl ReferenceMatrix { pub fn new(length: usize) -> Self { Self { bitvec: BitVec::repeat(false, length * length), length, } } pub fn references_for(&self, row: usize) -> impl Iterator + '_ { self.row_slice(row).iter_ones() } #[inline(always)] fn row_slice(&self, row: usize) -> &BitSlice { &self.bitvec[row * self.length..][..self.length] } #[inline(always)] pub fn set_row_col(&mut self, row: usize, col: usize, value: bool) { self.bitvec.set(row * self.length + col, value) } #[inline(always)] pub fn get(&self, index: usize) -> bool { self.bitvec[index] } #[inline(always)] pub fn get_row_col(&self, row: usize, col: usize) -> bool { self.bitvec[row * self.length + col] } pub fn is_recursive(&self, index: usize) -> bool { let mut scheduled = self.row_slice(index).to_bitvec(); let mut visited = self.row_slice(index).to_bitvec(); // yes this is a bit inefficient because rows are visited repeatedly. while scheduled.any() { for one in scheduled.iter_ones() { if one == index { return true; } visited |= self.row_slice(one) } // i.e. visited did not change if visited.count_ones() == scheduled.count_ones() { break; } scheduled |= &visited; } false } } // Topological sort and strongly-connected components // // Adapted from the Pathfinding crate v2.0.3 by Samuel Tardieu , // licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 // // The original source code can be found at: https://github.com/samueltardieu/pathfinding // // Thank you, Samuel! impl ReferenceMatrix { pub fn topological_sort_into_groups(&self) -> TopologicalSort { if self.length == 0 { return TopologicalSort::Groups { groups: Vec::new() }; } let mut preds_map: Vec = vec![0; self.length]; // this is basically summing the columns, I don't see a better way to do it for row in self.bitvec.chunks(self.length) { for succ in row.iter_ones() { preds_map[succ] += 1; } } let mut groups = Vec::>::new(); // the initial group contains all symbols with no predecessors let mut prev_group: Vec = preds_map .iter() .enumerate() .filter_map(|(node, &num_preds)| { if num_preds == 0 { Some(node as u32) } else { None } }) .collect(); if prev_group.is_empty() { let remaining: Vec = (0u32..self.length as u32).collect(); return TopologicalSort::HasCycles { groups: Vec::new(), nodes_in_cycle: remaining, }; } while preds_map.iter().any(|x| *x > 0) { let mut next_group = Vec::::new(); for node in &prev_group { for succ in self.references_for(*node as usize) { { let num_preds = preds_map.get_mut(succ).unwrap(); *num_preds = num_preds.saturating_sub(1); if *num_preds > 0 { continue; } } // NOTE: we use -1 to mark nodes that have no predecessors, but are already // part of an earlier group. That ensures nodes are added to just 1 group let count = preds_map[succ]; preds_map[succ] = -1; if count > -1 { next_group.push(succ as u32); } } } groups.push(std::mem::replace(&mut prev_group, next_group)); if prev_group.is_empty() { let remaining: Vec = (0u32..self.length as u32) .filter(|i| preds_map[*i as usize] > 0) .collect(); return TopologicalSort::HasCycles { groups, nodes_in_cycle: remaining, }; } } groups.push(prev_group); TopologicalSort::Groups { groups } } /// Get the strongly-connected components of the set of input nodes. pub fn strongly_connected_components(&self, nodes: &[u32]) -> Vec> { let mut params = Params::new(self.length, nodes); 'outer: loop { for (node, value) in params.preorders.iter().enumerate() { if let Preorder::Removed = value { continue; } recurse_onto(self.length, &self.bitvec, node, &mut params); continue 'outer; } let mut result = Vec::new(); for group in params.scc.components() { result.push(group.map(|v| v as u32).collect()); } break result; } } /// Get the strongly-connected components of the set of input nodes. pub fn strongly_connected_components_prim(&self, nodes: &[u32]) -> Sccs { let mut params = Params::new(self.length, nodes); 'outer: loop { for (node, value) in params.preorders.iter().enumerate() { if let Preorder::Removed = value { continue; } recurse_onto(self.length, &self.bitvec, node, &mut params); continue 'outer; } break params.scc; } } } pub(crate) enum TopologicalSort { /// There were no cycles, all nodes have been partitioned into groups Groups { groups: Vec> }, /// Cycles were found. All nodes that are not part of a cycle have been partitioned /// into groups. The other elements are in the `cyclic` vector. However, there may be /// many cycles, or just one big one. Use strongly-connected components to find out /// exactly what the cycles are and how they fit into the groups. HasCycles { groups: Vec>, nodes_in_cycle: Vec, }, } #[derive(Clone, Copy)] enum Preorder { Empty, Filled(usize), Removed, } struct Params { preorders: Vec, c: usize, p: Vec, s: Vec, scc: Sccs, scca: Vec, } impl Params { fn new(length: usize, group: &[u32]) -> Self { let mut preorders = vec![Preorder::Removed; length]; for value in group { preorders[*value as usize] = Preorder::Empty; } Self { preorders, c: 0, s: Vec::new(), p: Vec::new(), scc: Sccs { matrix: ReferenceMatrix::new(length), components: 0, }, scca: Vec::new(), } } } fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) { params.preorders[v] = Preorder::Filled(params.c); params.c += 1; params.s.push(v as u32); params.p.push(v as u32); for w in bitvec[v * length..][..length].iter_ones() { if !params.scca.contains(&(w as u32)) { match params.preorders[w] { Preorder::Filled(pw) => loop { let index = *params.p.last().unwrap(); match params.preorders[index as usize] { Preorder::Empty => unreachable!(), Preorder::Filled(current) => { if current > pw { params.p.pop(); } else { break; } } Preorder::Removed => {} } }, Preorder::Empty => recurse_onto(length, bitvec, w, params), Preorder::Removed => {} } } } if params.p.last() == Some(&(v as u32)) { params.p.pop(); while let Some(node) = params.s.pop() { params .scc .matrix .set_row_col(params.scc.components, node as usize, true); params.scca.push(node); params.preorders[node as usize] = Preorder::Removed; if node as usize == v { break; } } params.scc.components += 1; } } #[derive(Debug)] pub(crate) struct Sccs { components: usize, matrix: ReferenceMatrix, } impl Sccs { pub fn components(&self) -> impl Iterator + '_> + '_ { // work around a panic when requesting a chunk size of 0 let length = if self.matrix.length == 0 { // the `.take(self.components)` ensures the resulting iterator will be empty assert!(self.components == 0); 1 } else { self.matrix.length }; self.matrix .bitvec .chunks(length) .take(self.components) .map(|row| row.iter_ones()) } pub fn rows(&self) -> std::iter::Take> { // work around a panic when requesting a chunk size of 0 let length = if self.matrix.length == 0 { // the `.take(self.components)` ensures the resulting iterator will be empty assert!(self.components == 0); 1 } else { self.matrix.length }; self.matrix.bitvec.chunks(length).take(self.components) } }