Skip to main content

open_hypergraphs/strict/
graph.rs

1use super::relation::converse;
2use crate::array::{Array, ArrayKind, NaturalArray};
3use crate::category::Arrow;
4use crate::finite_function::FiniteFunction;
5use crate::indexed_coproduct::IndexedCoproduct;
6use crate::strict::hypergraph::Hypergraph;
7use num_traits::{One, Zero};
8
9/// Return the adjacency map for a [`Hypergraph`] `h`.
10///
11/// If `X` is the finite set of operations in `h`, then `operation_adjacency(h)` computes the
12/// indexed coproduct `adjacency : X → X*`, where the list `adjacency(x)` is all operations reachable in
13/// a single step from operation `x`.
14pub(crate) fn operation_adjacency<K: ArrayKind, O, A>(
15    h: &Hypergraph<K, O, A>,
16) -> IndexedCoproduct<K, FiniteFunction<K>>
17where
18    K::Type<K::I>: NaturalArray<K>,
19{
20    h.t.flatmap(&converse(&h.s))
21}
22
23/// Return the node-level adjacency map for a [`Hypergraph`].
24///
25/// If `W` is the finite set of nodes in `h`, then `node_adjacency(h)` computes the
26/// indexed coproduct `adjacency : W → W*`, where the list `adjacency(w)` is all nodes reachable in
27/// a single step from node `w`.
28pub(crate) fn node_adjacency<K: ArrayKind, O, A>(
29    h: &Hypergraph<K, O, A>,
30) -> IndexedCoproduct<K, FiniteFunction<K>>
31where
32    K::Type<K::I>: NaturalArray<K>,
33{
34    node_adjacency_from_incidence(&h.s, &h.t)
35}
36
37/// Return the node-level adjacency map from incidence data.
38///
39/// If `W` is the finite set of nodes, then the result is an indexed coproduct
40/// `adjacency : W → W*`, where `adjacency(w)` is all nodes reachable in a single step from `w`.
41pub(crate) fn node_adjacency_from_incidence<K: ArrayKind>(
42    s: &IndexedCoproduct<K, FiniteFunction<K>>,
43    t: &IndexedCoproduct<K, FiniteFunction<K>>,
44) -> IndexedCoproduct<K, FiniteFunction<K>>
45where
46    K::Type<K::I>: NaturalArray<K>,
47{
48    converse(s).flatmap(t)
49}
50
51/// A kahn-ish algorithm for topological sorting of an adjacency relation, encoded as an
52/// [`IndexedCoproduct`] (see [`converse`] for details)
53///
54pub(crate) fn kahn<K: ArrayKind>(
55    adjacency: &IndexedCoproduct<K, FiniteFunction<K>>,
56) -> (K::Index, K::Type<K::I>)
57where
58    K::Type<K::I>: NaturalArray<K>,
59{
60    // The layering assignment to each node.
61    // A mutable array of length n with values in {0..n}
62    let mut order: K::Type<K::I> = K::Type::<K::I>::fill(K::I::zero(), adjacency.len());
63    // Predicate determining if a node has been visited.
64    // 1 = unvisited
65    // 0 = visited
66    // NOTE: we store this as "NOT visited" so we can efficiently filter using "repeat".
67    let mut unvisited: K::Type<K::I> = K::Type::<K::I>::fill(K::I::one(), adjacency.len());
68    // Indegree of each node.
69    let mut indegree = indegree(adjacency);
70    // the set of nodes on the frontier, initialized to those with zero indegree.
71    let mut frontier: K::Index = zero(&indegree);
72
73    // Loop until frontier is empty, or at max possible layering depth.
74    let mut depth = K::I::zero();
75
76    // Implementation outline:
77    // 1. Compute *sparse* relative indegree, which is:
78    //      - idxs of reachable nodes
79    //      - counts of reachability from a given set
80    // 2. Subtract from global indegree array using scatter_sub_assign
81    //      - scatter_sub_assign::<K>(&mut indegree.table, &reachable_ix, &reachable_count.table);
82    // 3. Compute new frontier:
83    //      - Numpy-esque: `reachable_ix[indegree[reachable_ix] == 0 && unvisited[reachable_ix]]`
84    while !frontier.is_empty() && depth <= adjacency.len() {
85        // Mark nodes in the current frontier as visited
86        // unvisited[frontier] = 0;
87        unvisited.scatter_assign_constant(&frontier, K::I::zero());
88        // Set the order of nodes in the frontier to the current depth.
89        // order[frontier] = depth;
90        order.scatter_assign_constant(&frontier, depth.clone());
91
92        // For each node, compute the number of incoming edges from nodes in the frontier,
93        // and count paths to each.
94        let (reachable_ix, reachable_count) = sparse_relative_indegree(
95            adjacency,
96            &FiniteFunction::new(frontier, adjacency.len()).unwrap(),
97        );
98
99        // indegree = indegree - dense_relative_indegree(a, f)
100        indegree
101            .table
102            .as_mut()
103            .scatter_sub_assign(&reachable_ix.table, &reachable_count.table);
104
105        // Reachable nodes with zero indegree...
106        // frontier = reachable_ix[indegree[reachable_ix] == 0]
107        frontier = {
108            // *indices* i of reachable_ix such that indegree[reachable_ix[i]] == 0
109            let reachable_ix_indegree_zero_ix = indegree
110                .table
111                .gather(reachable_ix.table.get_range(..))
112                .zero();
113
114            // only nodes in reachable_ix with indegree 0
115            reachable_ix
116                .table
117                .gather(reachable_ix_indegree_zero_ix.get_range(..))
118        };
119
120        // .. and filter out those which have been visited.
121        // frontier = frontier[unvisited[frontier]]
122        frontier = filter::<K>(
123            &frontier,
124            &unvisited.as_ref().gather(frontier.get_range(..)),
125        );
126
127        // Increment depth
128        depth = depth + K::I::one();
129    }
130
131    (order.into(), unvisited)
132}
133
134/// Compute indegree of all nodes in a multigraph.
135pub(crate) fn indegree<K: ArrayKind>(
136    adjacency: &IndexedCoproduct<K, FiniteFunction<K>>,
137) -> FiniteFunction<K>
138where
139    K::Type<K::I>: NaturalArray<K>,
140{
141    // Indegree is *relative* indegree with respect to all nodes.
142    // PERFORMANCE: can compute this more efficiently by just bincounting adjacency directly.
143    dense_relative_indegree(adjacency, &FiniteFunction::<K>::identity(adjacency.len()))
144}
145
146/// Using the adjacency information in `adjacency`, compute the indegree of all nodes reachable from `f`.
147///
148/// More formally, define:
149///
150/// ```text
151/// a : Σ_{n ∈ A} s(n) → N  // the adjacency information of each
152/// f : K → N               // a subset of `K` nodes
153/// ```
154///
155/// Then `dense_relative_indegree(a, f)` computes the indegree from `f` of all `N` nodes.
156///
157/// # Returns
158///
159/// A finite function `N → E+1` denoting indegree of each node in `N` relative to `f`.
160pub(crate) fn dense_relative_indegree<K: ArrayKind>(
161    adjacency: &IndexedCoproduct<K, FiniteFunction<K>>,
162    f: &FiniteFunction<K>,
163) -> FiniteFunction<K>
164where
165    K::Type<K::I>: NaturalArray<K>,
166{
167    // Must have that the number of nodes `adjacency.len()`
168    assert_eq!(adjacency.len(), f.target());
169
170    // Operations reachable from those in the set f.
171    let reached = adjacency.indexed_values(f).unwrap();
172    // Counts in `table` can be as large as the number of reached incidences, which may exceed
173    // `adjacency.len()` when multiplicity is present.
174    let target = reached.table.len() + K::I::one();
175    let table = (reached.table.as_ref() as &K::Type<K::I>).bincount(adjacency.len());
176    FiniteFunction::new(table, target).unwrap()
177}
178
179/// Using the adjacency information in `adjacency`, compute the indegree of all nodes reachable from `f`.
180///
181/// More formally, let:
182///
183/// - `a : Σ_{n ∈ A} s(n) → N` denote the adjacency information of each
184/// - `f : K → N` be a subset of `K` nodes
185///
186/// Then `sparse_relative_indegree(a, f)` computes:
187///
188/// - `g : R → N`, the subset of (R)eachable nodes reachable from `f`
189/// - `i : R → E+1`, the *indegree* of nodes in `R`.
190///
191pub(crate) fn sparse_relative_indegree<K: ArrayKind>(
192    a: &IndexedCoproduct<K, FiniteFunction<K>>,
193    f: &FiniteFunction<K>,
194) -> (FiniteFunction<K>, FiniteFunction<K>)
195where
196    K::Type<K::I>: NaturalArray<K>,
197{
198    // Must have that the number of nodes `adjacency.len()`
199    assert_eq!(a.len(), f.target());
200
201    // Indices of operations reachable from those in the set f.
202    // Indices may appear more than once.
203    let g = a.indexed_values(f).unwrap();
204    let (i, c) = g.table.sparse_bincount();
205    // Counts in `c` can be as large as the number of reached incidences, which may exceed `a.len()`
206    // when multiplicity is present.
207    let target = g.table.len() + K::I::one();
208
209    (
210        FiniteFunction::new(i, a.len()).unwrap(),
211        FiniteFunction::new(c, target).unwrap(),
212    )
213}
214
215/// Given:
216///
217/// - `values : K → N`
218/// - `predicate : K → 2`
219///
220/// Return the subset of `values` for which `predicate(i) = 1`
221pub(crate) fn filter<K: ArrayKind>(values: &K::Index, predicate: &K::Index) -> K::Index {
222    predicate.repeat(values.get_range(..))
223}
224
225/// Given an array of indices `values` in `{0..N}` and a predicate `N → 2`, select select values `i` for
226/// which `predicate(i) = 1`.
227#[allow(dead_code)]
228fn filter_by_dense<K: ArrayKind>(values: &K::Index, predicate: &K::Index) -> K::Index
229where
230    K::Type<K::I>: NaturalArray<K>,
231{
232    predicate
233        .gather(values.get_range(..))
234        .repeat(values.get_range(..))
235}
236
237/// Given a FiniteFunction `X → L`, compute its converse,
238/// a relation `r : L → X*`, and return the result as an array of arrays,
239/// where `r_i` is the list of elements in `X` mapping to i.
240pub(crate) fn converse_iter<K: ArrayKind>(
241    order: FiniteFunction<K>,
242) -> impl Iterator<Item = K::Index>
243where
244    K::Type<K::I>: NaturalArray<K>,
245    K::I: Into<usize>,
246{
247    let c = converse(&IndexedCoproduct::elements(order));
248    c.into_iter().map(|x| x.table)
249}
250
251////////////////////////////////////////////////////////////////////////////////
252// Array trait helpers
253
254// FiniteFunction helpers
255pub(crate) fn zero<K: ArrayKind>(f: &FiniteFunction<K>) -> K::Index
256where
257    K::Type<K::I>: NaturalArray<K>,
258{
259    (f.table.as_ref() as &K::Type<K::I>).zero()
260}