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}