Skip to main content

fso_graph_builder/
graph_ops.rs

1use log::info;
2use rayon::prelude::*;
3
4use crate::graph::csr::{prefix_sum, Csr, SwapCsr};
5use crate::graph::Target;
6use crate::index::Idx;
7use crate::{
8    CsrLayout, DirectedDegrees, DirectedNeighborsWithValues, Error, Graph, SharedMut,
9    UndirectedDegrees, UndirectedNeighborsWithValues,
10};
11
12use std::ops::Range;
13use std::sync::Arc;
14use std::time::Instant;
15
16/// Partition the node set based on the degrees of the nodes.
17pub trait DegreePartitionOp<NI: Idx, EV> {
18    /// Creates a range-based degree partition of the nodes.
19    ///
20    /// Divide the nodes into `concurrency` number of ranges such that these
21    /// ranges have roughly equal total degree. That is, the sum of the degrees
22    /// of the nodes of each range should be roughly equal to the extent that
23    /// that's actually possible.
24    /// The length of the returned vector will never exceed `concurrency`.
25    fn degree_partition(&self, concurrency: usize) -> Vec<Range<NI>>;
26}
27
28/// Partition the node set based on the out degrees of the nodes.
29pub trait OutDegreePartitionOp<NI: Idx, EV> {
30    /// Creates a range-based out degree partition of the nodes.
31    ///
32    /// Divide the nodes into `concurrency` number of ranges such that these
33    /// ranges have roughly equal total out degree. That is, the sum of the out
34    /// degrees of the nodes of each range should be roughly equal to the extent
35    /// that that's actually possible.
36    /// The length of the returned vector will never exceed `concurrency`.
37    fn out_degree_partition(&self, concurrency: usize) -> Vec<Range<NI>>;
38}
39
40/// Partition the node set based on the in degrees of the nodes.
41pub trait InDegreePartitionOp<NI: Idx, EV> {
42    /// Creates a range-based in degree partition of the nodes.
43    ///
44    /// Divide the nodes into `concurrency` number of ranges such that these
45    /// ranges have roughly equal total in degree. That is, the sum of the in
46    /// degrees of the nodes of each range should be roughly equal to the extent
47    /// that that's actually possible.
48    /// The length of the returned vector will never exceed `concurrency`.
49    fn in_degree_partition(&self, concurrency: usize) -> Vec<Range<NI>>;
50}
51
52/// Call a particular function for each node with its corresponding state in parallel.
53pub trait ForEachNodeParallelOp<NI: Idx> {
54    /// For each node calls `node_fn` with the node and its corresponding mutable
55    /// state in parallel.
56    ///
57    /// For every node `n` in the graph `node_fn(&self, n, node_values[n.index()])`
58    /// will be called.
59    ///
60    /// `node_values` must have length exactly equal to the number of nodes in
61    /// the graph.
62    ///
63    /// # Example
64    ///
65    /// ```
66    /// # use fso_graph_builder::prelude::*;
67    /// # use std::ops::Range;
68    /// let graph: DirectedCsrGraph<u32> = GraphBuilder::new()
69    ///     .edges(vec![(0, 1), (0, 2), (1, 2)])
70    ///     .build();
71    /// let mut node_values = vec![0; 3];
72    ///
73    /// graph.
74    ///     for_each_node_par(&mut node_values, |g, node, node_state| {
75    ///         *node_state = g.out_degree(node);
76    ///     });
77    ///
78    /// assert_eq!(node_values[0], 2);
79    /// assert_eq!(node_values[1], 1);
80    /// assert_eq!(node_values[2], 0);
81    /// ```
82    fn for_each_node_par<T, F>(&self, node_values: &mut [T], node_fn: F) -> Result<(), Error>
83    where
84        T: Send,
85        F: Fn(&Self, NI, &mut T) + Send + Sync;
86}
87
88/// Call a particular function for each node with its corresponding state in parallel based on a
89/// partition.
90pub trait ForEachNodeParallelByPartitionOp<NI: Idx> {
91    /// For each node calls `node_fn` with the node and its corresponding
92    /// mutable state in parallel, using `partition` as a parallelization hint.
93    ///
94    /// For every node `n` in the graph `node_fn(&self, n, node_values[n.index()])`
95    /// will be called.
96    ///
97    /// `node_values` must have length exactly equal to the number of nodes in
98    /// the graph.
99    ///
100    /// The parallelization will be implemented such that the work for a set of
101    /// nodes represented by each range in `partition` will correspond to a task
102    /// that will run in a single thread.
103    ///
104    /// # Example
105    ///
106    /// ```
107    /// # use fso_graph_builder::prelude::*;
108    /// # use std::ops::Range;
109    /// let graph: DirectedCsrGraph<u32> = GraphBuilder::new()
110    ///     .edges(vec![(0, 1), (0, 2), (1, 2)])
111    ///     .build();
112    /// let mut node_values = vec![0; 3];
113    /// let partition: Vec<Range<u32>> = graph.out_degree_partition(num_cpus::get());
114    ///
115    /// graph.
116    ///     for_each_node_par_by_partition(&partition, &mut node_values, |g, node, node_state| {
117    ///         *node_state = g.out_degree(node);
118    ///     });
119    ///
120    /// assert_eq!(node_values[0], 2);
121    /// assert_eq!(node_values[1], 1);
122    /// assert_eq!(node_values[2], 0);
123    /// ```
124    fn for_each_node_par_by_partition<T, F>(
125        &self,
126        partition: &[Range<NI>],
127        node_values: &mut [T],
128        node_fn: F,
129    ) -> Result<(), Error>
130    where
131        T: Send,
132        F: Fn(&Self, NI, &mut T) + Send + Sync;
133}
134
135pub trait RelabelByDegreeOp<NI, EV> {
136    /// Creates a new graph by relabeling the node ids of the given graph.
137    ///
138    /// Ids are relabaled using descending degree-order, i.e., given `n` nodes,
139    /// the node with the largest degree will become node id `0`, the node with
140    /// the smallest degree will become node id `n - 1`.
141    ///
142    /// Note, that this method creates a new graph with the same space
143    /// requirements as the input graph.
144    ///
145    /// # Example
146    ///
147    /// ```
148    /// use fso_graph_builder::prelude::*;
149    ///
150    /// let mut graph: UndirectedCsrGraph<u32> = GraphBuilder::new()
151    ///     .edges(vec![(0, 1), (1, 2), (1, 3), (3, 0)])
152    ///     .build();
153    ///
154    /// assert_eq!(graph.degree(0), 2);
155    /// assert_eq!(graph.degree(1), 3);
156    /// assert_eq!(graph.degree(2), 1);
157    /// assert_eq!(graph.degree(3), 2);
158    ///
159    /// let mut neighbors = graph.neighbors(0);
160    /// assert_eq!(neighbors.next(), Some(&1));
161    /// assert_eq!(neighbors.next(), Some(&3));
162    /// assert_eq!(neighbors.next(), None);
163    ///
164    /// graph.make_degree_ordered();
165    ///
166    /// assert_eq!(graph.degree(0), 3);
167    /// assert_eq!(graph.degree(1), 2);
168    /// assert_eq!(graph.degree(2), 2);
169    /// assert_eq!(graph.degree(3), 1);
170    ///
171    /// assert_eq!(graph.neighbors(0).as_slice(), &[1, 2, 3]);
172    /// ```
173    fn make_degree_ordered(&mut self);
174}
175
176pub trait ToUndirectedOp {
177    type Undirected;
178
179    /// Creates a new undirected graph from the edges of an existing graph.
180    ///
181    /// Note, that this method creates a new graph with the same space
182    /// requirements as the input graph.
183    ///
184    /// # Example
185    ///
186    /// ```
187    /// use fso_graph_builder::prelude::*;
188    ///
189    /// let graph: DirectedCsrGraph<u32> = GraphBuilder::new()
190    ///     .edges(vec![(0, 1), (2, 0)])
191    ///     .build();
192    ///
193    /// assert_eq!(graph.out_degree(0), 1);
194    /// assert_eq!(graph.out_neighbors(0).as_slice(), &[1]);
195    ///
196    /// assert_eq!(graph.in_degree(0), 1);
197    /// assert_eq!(graph.in_neighbors(0).as_slice(), &[2]);
198    ///
199    /// let graph = graph.to_undirected(None);
200    ///
201    /// assert_eq!(graph.degree(0), 2);
202    /// assert_eq!(graph.neighbors(0).as_slice(), &[1, 2]);
203    /// ```
204    ///
205    /// This method accepts an optional [`CsrLayout`] as second parameter,
206    /// which has the same effect as described in [`GraphBuilder::csr_layout`]
207    ///
208    /// # Example
209    ///
210    /// ```
211    /// use fso_graph_builder::prelude::*;
212    ///
213    /// let graph: DirectedCsrGraph<u32> = GraphBuilder::new()
214    ///     .edges(vec![(0, 2), (1, 0), (2, 0)])
215    ///     .build();
216    ///
217    /// // No layout specified, a default layput is chosen
218    /// let un_graph = graph.to_undirected(None);
219    /// let mut neighbors = un_graph.neighbors(0).copied().collect::<Vec<_>>();
220    /// neighbors.sort_unstable();
221    /// assert_eq!(neighbors, &[1, 2, 2]);
222    ///
223    /// // The `Sorted` layout
224    /// let un_graph = graph.to_undirected(CsrLayout::Sorted);
225    /// assert_eq!(un_graph.neighbors(0).as_slice(), &[1, 2, 2]);
226    ///
227    /// // The `Deduplicated` layout
228    /// let un_graph = graph.to_undirected(CsrLayout::Deduplicated);
229    /// assert_eq!(un_graph.neighbors(0).as_slice(), &[1, 2]);
230    /// ```
231    fn to_undirected(&self, layout: impl Into<Option<CsrLayout>>) -> Self::Undirected;
232}
233
234pub trait SerializeGraphOp<W> {
235    fn serialize(&self, write: W) -> Result<(), Error>;
236}
237
238pub trait DeserializeGraphOp<R, G> {
239    fn deserialize(read: R) -> Result<G, Error>;
240}
241
242impl<G, NI, EV> RelabelByDegreeOp<NI, EV> for G
243where
244    NI: Idx,
245    EV: Copy + Ord + Sync,
246    G: Graph<NI>
247        + UndirectedDegrees<NI>
248        + UndirectedNeighborsWithValues<NI, EV>
249        + SwapCsr<NI, NI, EV>
250        + Sync,
251{
252    fn make_degree_ordered(&mut self) {
253        relabel_by_degree(self)
254    }
255}
256
257impl<NI, G> ForEachNodeParallelOp<NI> for G
258where
259    NI: Idx,
260    G: Graph<NI> + Sync,
261{
262    /// For each node calls a given function with the node and its corresponding
263    /// mutable state in parallel.
264    ///
265    /// The parallelization is done by means of a [rayon](https://docs.rs/rayon/)
266    /// based fork join with a task for each node.
267    fn for_each_node_par<T, F>(&self, node_values: &mut [T], node_fn: F) -> Result<(), Error>
268    where
269        T: Send,
270        F: Fn(&Self, NI, &mut T) + Send + Sync,
271    {
272        if node_values.len() != self.node_count().index() {
273            return Err(Error::InvalidNodeValues);
274        }
275
276        let node_fn = Arc::new(node_fn);
277
278        node_values
279            .into_par_iter()
280            .enumerate()
281            .for_each(|(i, node_state)| node_fn(self, NI::new(i), node_state));
282
283        Ok(())
284    }
285}
286
287impl<NI, G> ForEachNodeParallelByPartitionOp<NI> for G
288where
289    NI: Idx,
290    G: Graph<NI> + Sync,
291{
292    /// For each node calls a given function with the node and its corresponding
293    /// mutable state in parallel based on the provided node partition.
294    ///
295    /// The parallelization is done by means of a [rayon](https://docs.rs/rayon/)
296    /// based fork join with a task for each range in the provided node partition.
297    fn for_each_node_par_by_partition<T, F>(
298        &self,
299        partition: &[Range<NI>],
300        node_values: &mut [T],
301        node_fn: F,
302    ) -> Result<(), Error>
303    where
304        T: Send,
305        F: Fn(&Self, NI, &mut T) + Send + Sync,
306    {
307        if node_values.len() != self.node_count().index() {
308            return Err(Error::InvalidNodeValues);
309        }
310
311        if partition.iter().map(|r| r.end - r.start).sum::<NI>() != self.node_count() {
312            return Err(Error::InvalidPartitioning);
313        }
314
315        let node_fn = Arc::new(node_fn);
316
317        let node_value_splits = split_by_partition(partition, node_values);
318
319        node_value_splits
320            .into_par_iter()
321            .zip(partition.into_par_iter())
322            .for_each_with(node_fn, |node_fn, (mutable_chunk, range)| {
323                for (node_state, node) in mutable_chunk.iter_mut().zip(range.start.range(range.end))
324                {
325                    node_fn(self, node, node_state);
326                }
327            });
328
329        Ok(())
330    }
331}
332
333impl<NI, EV, U> DegreePartitionOp<NI, EV> for U
334where
335    NI: Idx,
336    U: Graph<NI> + UndirectedDegrees<NI> + UndirectedNeighborsWithValues<NI, EV>,
337{
338    /// Creates a greedy range-based degree partition of the nodes.
339    ///
340    /// It is greedy in the sense that it goes through the node set only once
341    /// and simply adds a new range to the result whenever the current range's
342    /// nodes' degrees sum up to at least the average node degree.
343    ///
344    /// # Example
345    ///
346    /// ```
347    /// # use fso_graph_builder::prelude::*;
348    /// # use std::ops::Range;
349    /// let graph: UndirectedCsrGraph<u32> = GraphBuilder::new()
350    ///     .edges(vec![(0, 1), (0, 2), (0, 3), (0, 3)])
351    ///     .build();
352    ///
353    /// let partition: Vec<Range<u32>> = graph.degree_partition(2);
354    ///
355    /// assert_eq!(partition.len(), 2);
356    /// assert_eq!(partition[0], 0..1);
357    /// assert_eq!(partition[1], 1..4);
358    /// ```
359    fn degree_partition(&self, concurrency: usize) -> Vec<Range<NI>> {
360        let batch_size = ((self.edge_count().index() * 2) as f64 / concurrency as f64).ceil();
361        greedy_node_map_partition(
362            |node| self.degree(node).index(),
363            self.node_count(),
364            batch_size as usize,
365            concurrency,
366        )
367    }
368}
369
370impl<NI, EV, D> OutDegreePartitionOp<NI, EV> for D
371where
372    NI: Idx,
373    D: Graph<NI> + DirectedDegrees<NI> + DirectedNeighborsWithValues<NI, EV>,
374{
375    /// Creates a greedy range-based out degree partition of the nodes.
376    ///
377    /// It is greedy in the sense that it goes through the node set only once
378    /// and simply adds a new range to the result whenever the current range's
379    /// nodes' out degrees sum up to at least the average node out degree.
380    ///
381    /// # Example
382    ///
383    /// ```
384    /// # use fso_graph_builder::prelude::*;
385    /// # use std::ops::Range;
386    /// let graph: DirectedCsrGraph<u32> = GraphBuilder::new()
387    ///     .edges(vec![(0, 1), (0, 2), (2, 1), (2, 3)])
388    ///     .build();
389    ///
390    /// let partition: Vec<Range<u32>> = graph.out_degree_partition(2);
391    ///
392    /// assert_eq!(partition.len(), 2);
393    /// assert_eq!(partition[0], 0..1);
394    /// assert_eq!(partition[1], 1..4);
395    /// ```
396    fn out_degree_partition(&self, concurrency: usize) -> Vec<Range<NI>> {
397        let batch_size = (self.edge_count().index() as f64 / concurrency as f64).ceil();
398        greedy_node_map_partition(
399            |node| self.out_degree(node).index(),
400            self.node_count(),
401            batch_size as usize,
402            concurrency,
403        )
404    }
405}
406
407impl<NI, EV, D> InDegreePartitionOp<NI, EV> for D
408where
409    NI: Idx,
410    D: Graph<NI> + DirectedDegrees<NI> + DirectedNeighborsWithValues<NI, EV>,
411{
412    /// Creates a greedy range-based in degree partition of the nodes.
413    ///
414    /// It is greedy in the sense that it goes through the node set only once
415    /// and simply adds a new range to the result whenever the current range's
416    /// nodes' in degrees sum up to at least the average node in degree.
417    ///
418    /// # Example
419    ///
420    /// ```
421    /// # use fso_graph_builder::prelude::*;
422    /// # use std::ops::Range;
423    /// let graph: DirectedCsrGraph<u32> = GraphBuilder::new()
424    ///     .edges(vec![(1, 0), (1, 2), (2, 0), (3, 2)])
425    ///     .build();
426    ///
427    /// let partition: Vec<Range<u32>> = graph.in_degree_partition(2);
428    ///
429    /// assert_eq!(partition.len(), 2);
430    /// assert_eq!(partition[0], 0..1);
431    /// assert_eq!(partition[1], 1..4);
432    /// ```
433    fn in_degree_partition(&self, concurrency: usize) -> Vec<Range<NI>> {
434        let batch_size = (self.edge_count().index() as f64 / concurrency as f64).ceil();
435        greedy_node_map_partition(
436            |node| self.in_degree(node).index(),
437            self.node_count(),
438            batch_size as usize,
439            concurrency,
440        )
441    }
442}
443
444// Split input slice into a vector of partition.len() disjoint slices such that
445// the slice at index i in the output vector has the same length as the range at
446// index i in the input partition.
447fn split_by_partition<'a, NI: Idx, T>(
448    partition: &[Range<NI>],
449    slice: &'a mut [T],
450) -> Vec<&'a mut [T]> {
451    debug_assert_eq!(
452        partition
453            .iter()
454            .map(|r| r.end - r.start)
455            .sum::<NI>()
456            .index(),
457        slice.len()
458    );
459
460    let mut splits = Vec::with_capacity(partition.len());
461
462    let mut remainder = slice;
463    let mut current_start = NI::zero();
464    for range in partition.iter() {
465        let next_end = range.end - current_start;
466        current_start += next_end;
467
468        let (left, right) = remainder.split_at_mut(next_end.index());
469
470        splits.push(left);
471        remainder = right;
472    }
473
474    splits
475}
476
477// Partition nodes 0..node_count().index() into at most max_batches ranges such
478// that the sums of node_map(node) for each range are roughly equal. It does so
479// greedily and therefore does not guarantee an optimally balanced range-based
480// partition.
481fn greedy_node_map_partition<NI, F>(
482    node_map: F,
483    node_count: NI,
484    batch_size: usize,
485    max_batches: usize,
486) -> Vec<Range<NI>>
487where
488    F: Fn(NI) -> usize,
489    NI: Idx,
490{
491    let mut partitions = Vec::with_capacity(max_batches);
492
493    let mut partition_size = 0;
494    let mut partition_start = NI::zero();
495    let upper_bound = node_count - NI::new(1);
496
497    for node in NI::zero().range(node_count) {
498        partition_size += node_map(node);
499
500        if (partitions.len() < max_batches - 1 && partition_size >= batch_size)
501            || node == upper_bound
502        {
503            let partition_end = node + NI::new(1);
504            partitions.push(partition_start..partition_end);
505            partition_size = 0;
506            partition_start = partition_end;
507        }
508    }
509
510    partitions
511}
512
513fn relabel_by_degree<NI, G, EV>(graph: &mut G)
514where
515    NI: Idx,
516    G: Graph<NI>
517        + UndirectedDegrees<NI>
518        + UndirectedNeighborsWithValues<NI, EV>
519        + SwapCsr<NI, NI, EV>
520        + Sync,
521    EV: Copy + Ord + Sync,
522{
523    let start = Instant::now();
524    let degree_node_pairs = sort_by_degree_desc(graph);
525    info!("Relabel: sorted degree-node-pairs in {:?}", start.elapsed());
526
527    let start = Instant::now();
528    let (degrees, nodes) = unzip_degrees_and_nodes(degree_node_pairs);
529    info!("Relabel: built degrees and id map in {:?}", start.elapsed());
530
531    let start = Instant::now();
532    let offsets = prefix_sum(degrees);
533    let targets = relabel_targets(graph, nodes, &offsets);
534    info!("Relabel: built and sorted targets in {:?}", start.elapsed());
535
536    graph.swap_csr(Csr::new(
537        offsets.into_boxed_slice(),
538        targets.into_boxed_slice(),
539    ));
540}
541
542// Extracts (degree, node_id) pairs from the given graph and sorts them by
543// degree descending.
544fn sort_by_degree_desc<NI, EV, G>(graph: &G) -> Vec<(NI, NI)>
545where
546    NI: Idx,
547    G: Graph<NI> + UndirectedDegrees<NI> + UndirectedNeighborsWithValues<NI, EV> + Sync,
548{
549    let node_count = graph.node_count().index();
550    let mut degree_node_pairs = Vec::with_capacity(node_count);
551
552    (0..node_count)
553        .into_par_iter()
554        .map(NI::new)
555        .map(|node_id| (graph.degree(node_id), node_id))
556        .collect_into_vec(&mut degree_node_pairs);
557    degree_node_pairs.par_sort_unstable_by(|left, right| left.cmp(right).reverse());
558
559    degree_node_pairs
560}
561
562// Unzips (degree, node-id) pairs into `degrees` and `nodes`
563//
564// `degrees` maps a new node id to its degree.
565// `nodes` maps the previous node id to the new node id.
566fn unzip_degrees_and_nodes<NI: Idx>(degree_node_pairs: Vec<(NI, NI)>) -> (Vec<NI>, Vec<NI>) {
567    let node_count = degree_node_pairs.len();
568    let mut degrees = Vec::<NI>::with_capacity(node_count);
569    let mut nodes = Vec::<NI>::with_capacity(node_count);
570    let nodes_ptr = SharedMut::new(nodes.as_mut_ptr());
571
572    (0..node_count)
573        .into_par_iter()
574        .map(|n| {
575            let (degree, node) = degree_node_pairs[n];
576
577            // SAFETY: node is the node_id from degree_node_pairs which is
578            // created from 0..node_count -- the values are all distinct and we
579            // will not write into the same location in parallel
580            unsafe {
581                nodes_ptr.add(node.index()).write(NI::new(n));
582            }
583
584            degree
585        })
586        .collect_into_vec(&mut degrees);
587
588    // SAFETY: degree_node_pairs contains each value in 0..node_count once
589    unsafe {
590        nodes.set_len(node_count);
591    }
592
593    (degrees, nodes)
594}
595
596// Relabel target ids according to the given node mapping and offsets.
597fn relabel_targets<NI, EV, G>(graph: &G, nodes: Vec<NI>, offsets: &[NI]) -> Vec<Target<NI, EV>>
598where
599    NI: Idx,
600    G: Graph<NI> + UndirectedNeighborsWithValues<NI, EV> + Sync,
601    EV: Copy + Ord + Sync,
602{
603    let node_count = graph.node_count().index();
604    let edge_count = offsets[node_count].index();
605    let mut targets = Vec::<Target<NI, EV>>::with_capacity(edge_count);
606    let targets_ptr = SharedMut::new(targets.as_mut_ptr());
607
608    (0..node_count).into_par_iter().map(NI::new).for_each(|u| {
609        let new_u = nodes[u.index()];
610        let start_offset = offsets[new_u.index()].index();
611        let mut end_offset = start_offset;
612
613        for &v in graph.neighbors_with_values(u) {
614            let new_v = nodes[v.target.index()];
615            // SAFETY: a node u is processed by at most one thread. We write
616            // into a non-overlapping range defined by the offsets for that
617            // node. No two threads will write into the same range.
618            unsafe {
619                targets_ptr
620                    .add(end_offset)
621                    .write(Target::new(new_v, v.value));
622            }
623            end_offset += 1;
624        }
625
626        // SAFETY: start_offset..end_offset is a non-overlapping range for
627        // a node u which is processed by exactly one thread.
628        unsafe {
629            std::slice::from_raw_parts_mut(targets_ptr.add(start_offset), end_offset - start_offset)
630        }
631        .sort_unstable();
632    });
633
634    // SAFETY: we inserted every relabeled target id of which there are edge_count many.
635    unsafe {
636        targets.set_len(edge_count);
637    }
638
639    targets
640}
641
642#[cfg(test)]
643mod tests {
644    use crate::{
645        builder::GraphBuilder, graph::csr::UndirectedCsrGraph, graph_ops::unzip_degrees_and_nodes,
646        UndirectedNeighbors,
647    };
648
649    use super::*;
650
651    #[test]
652    fn split_by_partition_3_parts() {
653        let partition = vec![0..2, 2..5, 5..10];
654        let mut slice = (0..10).collect::<Vec<_>>();
655        let splits = split_by_partition(&partition, &mut slice);
656
657        assert_eq!(splits.len(), partition.len());
658        for (s, p) in splits.into_iter().zip(partition) {
659            assert_eq!(s, p.into_iter().collect::<Vec<usize>>());
660        }
661    }
662
663    #[test]
664    fn split_by_partition_8_parts() {
665        let partition = vec![0..1, 1..2, 2..3, 3..4, 4..6, 6..7, 7..8, 8..10];
666        let mut slice = (0..10).collect::<Vec<_>>();
667        let splits = split_by_partition(&partition, &mut slice);
668
669        assert_eq!(splits.len(), partition.len());
670        for (s, p) in splits.into_iter().zip(partition) {
671            assert_eq!(s, p.into_iter().collect::<Vec<usize>>());
672        }
673    }
674
675    #[test]
676    fn greedy_node_map_partition_1_part() {
677        let partitions = greedy_node_map_partition::<usize, _>(|_| 1_usize, 10, 10, 99999);
678        assert_eq!(partitions.len(), 1);
679        assert_eq!(partitions[0], 0..10);
680    }
681
682    #[test]
683    fn greedy_node_map_partition_2_parts() {
684        let partitions = greedy_node_map_partition::<usize, _>(|x| x % 2_usize, 10, 4, 99999);
685        assert_eq!(partitions.len(), 2);
686        assert_eq!(partitions[0], 0..8);
687        assert_eq!(partitions[1], 8..10);
688    }
689
690    #[test]
691    fn greedy_node_map_partition_6_parts() {
692        let partitions = greedy_node_map_partition::<usize, _>(|x| x, 10, 6, 99999);
693        assert_eq!(partitions.len(), 6);
694        assert_eq!(partitions[0], 0..4);
695        assert_eq!(partitions[1], 4..6);
696        assert_eq!(partitions[2], 6..7);
697        assert_eq!(partitions[3], 7..8);
698        assert_eq!(partitions[4], 8..9);
699        assert_eq!(partitions[5], 9..10);
700    }
701
702    #[test]
703    fn greedy_node_map_partition_max_batches() {
704        let partitions = greedy_node_map_partition::<usize, _>(|x| x, 10, 6, 3);
705        assert_eq!(partitions.len(), 3);
706        assert_eq!(partitions[0], 0..4);
707        assert_eq!(partitions[1], 4..6);
708        assert_eq!(partitions[2], 6..10);
709    }
710
711    #[test]
712    fn sort_by_degree_test() {
713        let graph: UndirectedCsrGraph<_> = GraphBuilder::new()
714            .edges::<u32, _>(vec![
715                (0, 1),
716                (1, 2),
717                (1, 3),
718                (2, 0),
719                (2, 1),
720                (2, 3),
721                (3, 0),
722                (3, 2),
723            ])
724            .build();
725
726        assert_eq!(
727            sort_by_degree_desc(&graph),
728            vec![(5, 2), (4, 3), (4, 1), (3, 0)]
729        );
730    }
731
732    #[test]
733    fn unzip_degrees_and_nodes_test() {
734        let degrees_and_nodes = vec![(5, 2), (4, 3), (4, 1), (3, 0)];
735
736        let (degrees, nodes) = unzip_degrees_and_nodes::<u32>(degrees_and_nodes);
737
738        assert_eq!(degrees, vec![5, 4, 4, 3]);
739        assert_eq!(nodes, vec![3, 2, 0, 1]);
740    }
741
742    #[test]
743    fn relabel_by_degree_test() {
744        let mut graph: UndirectedCsrGraph<_> = GraphBuilder::new()
745            .edges::<u32, _>(vec![
746                (0, 1),
747                (1, 2),
748                (1, 3),
749                (2, 0),
750                (2, 1),
751                (2, 3),
752                (3, 0),
753                (3, 2),
754            ])
755            .build();
756
757        graph.make_degree_ordered();
758
759        assert_eq!(graph.node_count(), graph.node_count());
760        assert_eq!(graph.edge_count(), graph.edge_count());
761
762        // old -> new
763        //   0 -> 3
764        //   1 -> 2
765        //   2 -> 0
766        //   3 -> 1
767        assert_eq!(graph.degree(0), 5);
768        assert_eq!(graph.degree(1), 4);
769        assert_eq!(graph.degree(2), 4);
770        assert_eq!(graph.degree(3), 3);
771
772        assert_eq!(graph.neighbors(0).as_slice(), &[1, 1, 2, 2, 3]);
773        assert_eq!(graph.neighbors(1).as_slice(), &[0, 0, 2, 3]);
774        assert_eq!(graph.neighbors(2).as_slice(), &[0, 0, 1, 3]);
775        assert_eq!(graph.neighbors(3).as_slice(), &[0, 1, 2]);
776    }
777}