1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
//! GraphRox is a network graph library for efficiently generating approximations of graphs.
//! GraphRox additionally provides a high-fidelity, lossy graph compression algorithm.
//!
//! # Graph Basics
//!
//! The `graphrox::Graph` struct is a basic, unweighted graph structure and the
//! `graphrox::GraphRepresentation` trait defines some methods for some basic graph operations.
//! Edges and vertices can be added to or removed from a graph. Each vertex in a graph has an
//! ID, indexed from zero. If an edge is created from the vertex with ID 3 to the vertex with
//! ID 5, vertices with IDs 0, 1, 2, and 4 are created implicitly.
//!
//! ```
//! use graphrox::{Graph, GraphRepresentation};
//!
//! let mut graph = Graph::new_directed();
//! graph.add_edge(3, 5);
//!
//! // Vertices 0 through 5 have been defined
//! assert_eq!(graph.vertex_count(), 6);
//! assert!(graph.does_edge_exist(3, 5));
//!
//! let edges_to_2 = [4, 0, 5, 1];
//! graph.add_vertex(2, Some(&edges_to_2));
//!
//! assert_eq!(graph.edge_count(), 5);
//!
//! // Add a vertex with ID 8 and no edges. This implicitly defines all vertices IDs less
//! // than 8
//! graph.add_vertex(8, None);
//!
//! assert_eq!(graph.vertex_count(), 9);
//! assert_eq!(graph.edge_count(), 5);
//!
//! // Edges can be removed
//! graph.delete_edge(2, 5);
//!
//! assert_eq!(graph.edge_count(), 4);
//! assert!(!graph.does_edge_exist(2, 5));
//! ```
//!
//! # Graph Approximations
//!
//! A graph can be approximated into a `graphrox::Graph` with a lower dimensionality. This is
//! done by average pooling blocks of a given dimension in the adjacency matrix representation
//! of the graph, then applying a threshold to the average pooled matrix to determine which
//! entries in the adjacency matrix of the resulting graph will be 1 rather than 0.
//!
//! ```
//! use graphrox::{Graph, GraphRepresentation};
//!
//! let mut graph = Graph::new_directed();
//!
//! graph.add_vertex(0, Some(&[1, 2, 6]));
//! graph.add_vertex(1, Some(&[1, 2]));
//! graph.add_vertex(2, Some(&[0, 1]));
//! graph.add_vertex(3, Some(&[1, 2, 4]));
//! graph.add_vertex(5, Some(&[6, 7]));
//! graph.add_vertex(6, Some(&[6]));
//! graph.add_vertex(7, Some(&[6]));
//!
//! // Average pool 2x2 blocks in the graph's adjacency matrix, then apply a threshold of 0.5,
//! // or 50%. Any blocks with at least 50% of their entries being 1 (rather than 0) will be
//! // represented with a 1 in the adjacency matrix of the resulting graph.
//! let approx_graph = graph.approximate(2, 0.5);
//!
//! println!("{}", graph.matrix_string());
//! println!();
//! println!("{}", approx_graph.matrix_string());
//!
//! /* Ouput:
//!
//! [ 0, 0, 1, 0, 0, 0, 0, 0 ]
//! [ 1, 1, 1, 1, 0, 0, 0, 0 ]
//! [ 1, 1, 0, 1, 0, 0, 0, 0 ]
//! [ 0, 0, 0, 0, 0, 0, 0, 0 ]
//! [ 0, 0, 0, 1, 0, 0, 0, 0 ]
//! [ 0, 0, 0, 0, 0, 0, 0, 0 ]
//! [ 1, 0, 0, 0, 0, 1, 1, 1 ]
//! [ 0, 0, 0, 0, 0, 1, 0, 0 ]
//!
//! [ 1, 1, 0, 0 ]
//! [ 1, 0, 0, 0 ]
//! [ 0, 0, 0, 0 ]
//! [ 0, 0, 1, 1 ]
//!
//! */
//! ```
//!
//! Additionally, a graph can be average pooled without applying a threshold:
//!
//! ```
//! use graphrox::{Graph, GraphRepresentation};
//!
//! let mut graph = Graph::new_directed();
//!
//! graph.add_vertex(0, Some(&[1, 2, 6]));
//! graph.add_vertex(1, Some(&[1, 2]));
//! graph.add_vertex(2, Some(&[0, 1]));
//! graph.add_vertex(3, Some(&[1, 2, 4]));
//! graph.add_vertex(5, Some(&[6, 7]));
//! graph.add_vertex(6, Some(&[6]));
//! graph.add_vertex(7, Some(&[6]));
//!
//! let avg_pool_matrix = graph.find_avg_pool_matrix(2);
//!
//! println!("{}", graph.matrix_string());
//! println!();
//! println!("{}", avg_pool_matrix.to_string());
//!
//! /* Ouput:
//!
//! [ 0, 0, 1, 0, 0, 0, 0, 0 ]
//! [ 1, 1, 1, 1, 0, 0, 0, 0 ]
//! [ 1, 1, 0, 1, 0, 0, 0, 0 ]
//! [ 0, 0, 0, 0, 0, 0, 0, 0 ]
//! [ 0, 0, 0, 1, 0, 0, 0, 0 ]
//! [ 0, 0, 0, 0, 0, 0, 0, 0 ]
//! [ 1, 0, 0, 0, 0, 1, 1, 1 ]
//! [ 0, 0, 0, 0, 0, 1, 0, 0 ]
//!
//! [ 0.50, 0.75, 0.00, 0.00 ]
//! [ 0.50, 0.25, 0.00, 0.00 ]
//! [ 0.00, 0.25, 0.00, 0.00 ]
//! [ 0.25, 0.00, 0.50, 0.50 ]
//!
//! */
//! ```
//!
//! # Graph Compression
//!
//! Graphs can be compressed into a space-efficient form. Using the same approximation
//! technique mentioned above, a threshold can be applied to 8x8 blocks in a graph's adjacency
//! matrix. If a given block in the average pooling matrix meets the threshold, the entire
//! block will be losslessly encoded in an unsigned 64-bit integer. If the block does not meet
//! the threshold, the entire block will be represented by a 0 in the resulting matrix. Because
//! GraphRox stores matrices as adjacency lists, 0 entries have no effect on storage size.
//!
//! `compression_level` is divided by 64 to obtain the threshold. Thus, `compression_level` is
//! equal to the number of entries in an 8x8 block of the adjacency matrix that must be ones in
//! order for the block to be losslessly encoded in the CompressedGraph. A CompressedGraph is
//! not necessarily approximated, though, because the `compression_level` may be one.
//! `compression_level` will be clamped to a number between 1 and 64 inclusive.
//!
//! ```
//! use graphrox::{Graph, GraphRepresentation};
//!
//! let mut graph = Graph::new_directed();
//! graph.add_vertex(23, None);
//!
//! for i in 8..16 {
//! for j in 8..16 {
//! graph.add_edge(i, j);
//! }
//! }
//!
//! for i in 0..8 {
//! for j in 0..4 {
//! graph.add_edge(i, j);
//! }
//! }
//!
//! graph.add_edge(22, 18);
//! graph.add_edge(15, 18);
//!
//! let compressed_graph = graph.compress(2);
//!
//! assert_eq!(compressed_graph.vertex_count(), 24);
//! assert_eq!(compressed_graph.edge_count(), 96); // 64 + 32
//!
//! // Because half of the 8x8 block was filled, half of the bits in the u64 are ones.
//! assert_eq!(compressed_graph.get_compressed_matrix_entry(0, 0), 0x00000000ffffffffu64);
//!
//! // Because the entire 8x8 block was filled, the block is represented with u64::MAX
//! assert_eq!(compressed_graph.get_compressed_matrix_entry(1, 1), u64::MAX);
//! ```
//!
//! Compressing a graph yields a `graphrox::CompressedGraph`. `CompressedGraph`s can be easily
//! decompressed back into a `graphrox::Graph`:
//!
//! ```
//! use graphrox::{Graph, GraphRepresentation};
//!
//! let mut graph = Graph::new_undirected();
//!
//! graph.add_vertex(0, Some(&[1, 2, 6]));
//! graph.add_vertex(3, Some(&[1, 2]));
//!
//! let compressed_graph = graph.compress(4);
//! let decompressed_graph = compressed_graph.decompress();
//!
//! assert_eq!(graph.edge_count(), decompressed_graph.edge_count());
//! assert_eq!(graph.vertex_count(), decompressed_graph.vertex_count());
//!
//! for (from_vertex, to_vertex) in &decompressed_graph {
//! assert!(graph.does_edge_exist(from_vertex, to_vertex));
//! }
//! ```
//!
//! # Saving graphs to disk
//!
//! GraphRox provides `to_bytes()` and `try_from::<&[u8]>()` methods on `graphrox::Graph` and
//! `graphrox::CompressedGraph` which convert to and from efficient big-endian byte-array
//! representations of graphs. The byte arrays are perfect for saving to disk or sending over
//! a websocket.
//!
//! ```
//! use graphrox::{CompressedGraph, Graph, GraphRepresentation};
//! use std::fs;
//!
//! let mut graph = Graph::new_undirected();
//!
//! graph.add_vertex(0, Some(&[1, 2, 6]));
//! graph.add_vertex(3, Some(&[1, 2]));
//!
//! // Convert the graph to bytes
//! let graph_bytes = graph.to_bytes();
//!
//! // Save the bytes to a file
//! fs::write("my-graph.gphrx", graph_bytes).unwrap();
//!
//! // Read the bytes from a file (then delete the file)
//! let graph_file = fs::read("my-graph.gphrx").unwrap();
//! fs::remove_file("my-graph.gphrx").unwrap();
//!
//! let graph_from_bytes = Graph::try_from(graph_file.as_slice()).unwrap();
//!
//! assert_eq!(graph.edge_count(), graph_from_bytes.edge_count());
//!
//! for (from_vertex, to_vertex) in &graph_from_bytes {
//! assert!(graph.does_edge_exist(from_vertex, to_vertex));
//! }
//!
//! // Compressed graphs can be converted to bytes as well
//! let compressed_graph = graph.compress(2);
//! fs::write("compressed-graph.cgphrx", compressed_graph.to_bytes()).unwrap();
//!
//! // Read the compressed_graph from a file (then delete the file)
//! let compressed_graph_file = fs::read("compressed-graph.cgphrx").unwrap();
//! fs::remove_file("compressed-graph.cgphrx").unwrap();
//!
//! let compressed_graph_from_bytes =
//! CompressedGraph::try_from(compressed_graph_file.as_slice()).unwrap();
//!
//! assert_eq!(compressed_graph_from_bytes.edge_count(), compressed_graph.edge_count());
//! ```
/// Errors that can be returned from GraphRox functions.
/// Utilities for working with GraphRox graphs.
/// Sparse GraphRox matrices in CSR format using efficient HashMaps and HashSets.
pub use CompressedGraph;
pub use GraphRepresentation;
pub use StandardGraph as Graph;
/// A small collection of utilities for constructing graphs with low performance overhead.
/// For performance reasons, constraints are not checked on these builders. Users of the
/// builder interfaces must provide correct data for the graph to be valid. Use these
/// builders at your own peril and be sure to read the documentation carefully to learn
/// the constraints of each method.