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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
//
// SPDX-License-Identifier: GPL-3.0-or-later
//! ParaGraph: Parallel graph processing.
//!
//! This crate implements datastructures and algorithms for concurrent traversal of graphs.
//! Graphs can be processed using 'operators'. An operator can see only a small part of a graph, namely
//! the 'active' node and its direct neighbors. Labelling operators can edit the associated data of the active
//! node and they can generate a set of new nodes which should be processed later. The order how the nodes are processed
//! is largely defined by the 'worklists'.
//!
//! # Operators
//! There are the following types of operators:
//! * [`ReadonlyOperator`] - Can only access graph elements by immutable reference. Needs to use interior mutability if modification is necessary.
//! * [`LabellingOperator`] - Can modify the local node data. The executor must provide a mutable reference to local node data.
//!
//! # Executors
//! There are the following executors:
//! * [`executors::single_thread::SingleThreadExecutor`] - Executes an operator in the current thread.
//! * [`executors::multi_thread::MultiThreadExecutor`] - Executes an operator on many threads. Imposes stricter trait bounds on operators and graph data structures.
//!
//!
//! # Example: compute cone of influence using atomics
//!
//! The following example visits the output cone of the `src` node. The output cone consists of all nodes
//! which can be reached by starting at `src` and then following outgoing edges.
//! Additionally, for each node in the cone, the operator keeps track of input nodes which are in the cone.
//!
//! Similar algorithms can for example be used to mark the regions of interest for incremental updates for shortest path searches.
//!
//! This algorithm is implemented as a [`ReadonlyOperator`] which operates on immutable references of the node data.
//! Safe mutability of the node data is still achieved using atomics. This avoids wrapping the node data into a
//! [`DataCell`] for locking.
//!
//! ```
//! use pargraph::prelude::*;
//! use petgraph::data::DataMap;
//! use petgraph::graph::DiGraph;
//! use petgraph::visit::*;
//! use std::sync::atomic::AtomicU32;
//!
//! struct NodeData {
//! /// Count the number of input edges to the node
//! /// which are part of the cone.
//! num_dependencies: AtomicU32,
//! }
//!
//! impl NodeData {
//! fn new() -> Self {
//! Self {
//! num_dependencies: AtomicU32::new(0),
//! }
//! }
//! }
//!
//! // Create a graph like:
//! // x---
//! // | \
//! // src y
//! // |
//! // a
//! // / \
//! // b c
//! // \ /
//! // d
//! let mut g = DiGraph::new();
//!
//! // Helper function for creating new nodes with default node data.
//! // Initialize the distance to the maximum value.
//! let mut new_node = || g.add_node(NodeData::new());
//!
//! // Create some new nodes.
//! let [x, y, src, a, b, c, d] = [(); 7].map(|_| new_node());
//!
//! // Add some edges (without any weights).
//! g.add_edge(x, src, ());
//! g.add_edge(x, y, ());
//! g.add_edge(src, a, ());
//! g.add_edge(a, b, ());
//! g.add_edge(a, c, ());
//! g.add_edge(c, d, ());
//! g.add_edge(b, d, ());
//!
//! let operator = ConeOfInfluenceOp {};
//!
//! let executor = MultiThreadExecutor::new();
//!
//! // Create a worklist and add the source node.
//! let wl = FifoWorklist::new_with_local_queues(vec![src].into());
//! executor.run_readonly(wl, &operator, &g);
//!
//! let get_num_dependencies = |n: petgraph::graph::NodeIndex| -> u32 {
//! g.node_weight(n)
//! .unwrap()
//! .num_dependencies
//! .load(std::sync::atomic::Ordering::Relaxed)
//! };
//!
//! // Check the distances.
//! assert_eq!(get_num_dependencies(x), 0, "x is not in the cone of influence of src");
//! assert_eq!(get_num_dependencies(y), 0, "y is not in the cone of influence of src");
//! assert_eq!(get_num_dependencies(src), 0);
//! assert_eq!(get_num_dependencies(a), 1);
//! assert_eq!(get_num_dependencies(b), 1);
//! assert_eq!(get_num_dependencies(c), 1);
//! assert_eq!(get_num_dependencies(d), 2);
//!
//! // This is our operator.
//! struct ConeOfInfluenceOp {}
//!
//! // We can implement this operator as a `ReadonlyOperator` because it does not require
//! // a mutable reference to the node data. Safe mutability is achieved using atomics.
//! // Note that we implement the operator for the reference type. Operators are required to implement `Clone`.
//! // A reference implements `Clone` automatically. Alternatively we could also derive `Clone` for `ConeOfInfluenceOp`
//! // and pass ownership of the operator to the executor. The executor might create clones of the operators for the worker
//! // threads.
//! impl<G> ReadonlyOperator<G> for &ConeOfInfluenceOp
//! where
//! G: GraphBase + IntoEdgesDirected,
//! G: DataMap<NodeWeight = NodeData>,
//! {
//! type WorkItem = G::NodeId;
//!
//! fn op(
//! &self,
//! active_node: Self::WorkItem,
//! local_view: LocalGraphView<&G>,
//! mut worklist: impl WorklistPush<Self::WorkItem>,
//! ) {
//! let output_nodes =
//! local_view.neighbors_directed(active_node, petgraph::Direction::Outgoing);
//!
//! for n in output_nodes {
//! // Access the node weight.
//! let n_data = local_view
//! .node_weight(n)
//! .expect("all nodes should have a weight");
//!
//! // Atomically increment the number of dependencies of the node `n`.
//! // `fetch_add` returns the previous value. If the previous value is `0` then
//! // we know that this is the first time we look at node `n` (unless there is a cycle leading to the source node).
//! let previous_num_dependencies = n_data
//! .num_dependencies
//! .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
//!
//! if previous_num_dependencies == 0 {
//! // This is the first time n is touched.
//! worklist.push(n);
//! }
//! }
//! }
//! }
//! ```
use ;
use GraphBase;
use ;
/// An graph operator which can only modify labels of nodes and edges
/// but cannot change the topology of a graph.
/// Operator which only gets read access to the graph.
/// The implementation can still write to graph/edge weights
/// but needs to care about synchronization itself by using apropriate
/// schemes (for example `RwLock` and atomics).
/// A data conflict error which can happen in two ways:
/// 1) The executor tries to get write access to the data of the active node but there's already some other threads reading or writing to it.
/// 2) The operator tries to get read access to data of other nodes/edges but the data is already locked for write access.
/// Either a node or edge ID.
/// Bundle read/write locking errors into one type.
/// Wrapper for data associated with graph elements.
/// Used for interior mutability during parallel graph processing.
pub type DataCell<T> = ;
/// This trait is used to allow using arbitrary data types for node weights
/// as long as it is possible to use a `DataCell` from them.
// /// Bundle of a graph node and a priority.
// /// This type implements `PartialOrd` and `Ord` based on the value of the priority (the value of the node is ignored).
// /// Priorities can be disabled by using the `()` type for priorities.
// /// Then the memory representation of this struct is equal to the one of the type used for graph nodes.
// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
// pub struct WithPriority<N, P> {
// /// Priority associated with the graph node. Can be of type `()` to disable priorities.
// pub priority: P,
// /// ID of the graph node.
// pub node: N,
// }
//
// impl<N> From<N> for WithPriority<N, ()> {
// fn from(node: N) -> WithPriority<N, ()> {
// WithPriority::new(node)
// }
// }
//
// impl<N, P> From<(N, P)> for WithPriority<N, P> {
// fn from((node, priority): (N, P)) -> Self {
// Self::new_with_priority(node, priority)
// }
// }
//
// impl<N, P> WithPriority<N, P> {
// pub fn new_with_priority(node: N, priority: P) -> Self {
// Self { priority, node }
// }
// }
//
// impl<N> WithPriority<N, ()> {
// /// Create a new struct with priorities disabled.
// pub fn new(node: N) -> Self {
// Self { priority: (), node }
// }
// }
//
// impl<N, P> PartialOrd for WithPriority<N, P>
// where
// P: PartialOrd,
// N: PartialEq,
// {
// fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
// self.priority.partial_cmp(&other.priority)
// }
// }
//
// impl<N, P> Ord for WithPriority<N, P>
// where
// P: Ord,
// N: PartialEq + Eq,
// {
// fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// self.priority.cmp(&other.priority)
// }
// }