souffle  2.0.2-371-g6315b36
UnionFind.h
Go to the documentation of this file.
1 /*
2  * Souffle - A Datalog Compiler
3  * Copyright (c) 2017 The Souffle Developers. All rights reserved
4  * Licensed under the Universal Permissive License v 1.0 as shown at:
5  * - https://opensource.org/licenses/UPL
6  * - <souffle root>/licenses/SOUFFLE-UPL.txt
7  */
8 
9 /************************************************************************
10  *
11  * @file UnionFind.h
12  *
13  * Defines a union-find data-structure
14  *
15  ***********************************************************************/
16 
17 #pragma once
18 
21 #include <atomic>
22 #include <cstddef>
23 #include <cstdint>
24 #include <functional>
25 #include <utility>
26 
27 namespace souffle {
28 
29 // branch predictor hacks
30 #define unlikely(x) __builtin_expect((x), 0)
31 #define likely(x) __builtin_expect((x), 1)
32 
33 using rank_t = uint8_t;
34 /* technically uint56_t, but, doesn't exist. Just be careful about storing > 2^56 elements. */
35 using parent_t = uint64_t;
36 
37 // number of bits that the rank is
38 constexpr uint8_t split_size = 8u;
39 
40 // block_t stores parent in the upper half, rank in the lower half
41 using block_t = uint64_t;
42 // block_t & rank_mask extracts the rank
43 constexpr block_t rank_mask = (1ul << split_size) - 1;
44 
45 /**
46  * Structure that emulates a Disjoint Set, i.e. a data structure that supports efficient union-find operations
47  */
48 class DisjointSet {
49  template <typename TupleType>
50  friend class EquivalenceRelation;
51 
53 
54 public:
55  DisjointSet() = default;
56 
57  // copy ctor
58  DisjointSet(DisjointSet& other) = delete;
59  // move ctor
60  DisjointSet(DisjointSet&& other) = delete;
61 
62  // copy assign ctor
63  DisjointSet& operator=(DisjointSet& ds) = delete;
64  // move assign ctor
65  DisjointSet& operator=(DisjointSet&& ds) = delete;
66 
67  /**
68  * Return the number of elements in this disjoint set (not the number of pairs)
69  */
70  inline size_t size() {
71  auto sz = a_blocks.size();
72  return sz;
73  };
74 
75  /**
76  * Yield reference to the node by its node index
77  * @param node node to be searched
78  * @return the parent block of the specified node
79  */
80  inline std::atomic<block_t>& get(parent_t node) const {
81  auto& ret = a_blocks.get(node);
82  return ret;
83  };
84 
85  /**
86  * Equivalent to the find() function in union/find
87  * Find the highest ancestor of the provided node - flattening as we go
88  * @param x the node to find the parent of, whilst flattening its set-tree
89  * @return The parent of x
90  */
92  // while x's parent is not itself
93  while (x != b2p(get(x))) {
94  block_t xState = get(x);
95  // yield x's parent's parent
96  parent_t newParent = b2p(get(b2p(xState)));
97  // construct block out of the original rank and the new parent
98  block_t newState = pr2b(newParent, b2r(xState));
99 
100  this->get(x).compare_exchange_strong(xState, newState);
101 
102  x = newParent;
103  }
104  return x;
105  }
106 
107 private:
108  /**
109  * Update the root of the tree of which x is, to have y as the base instead
110  * @param x : old root
111  * @param oldrank : old root rank
112  * @param y : new root
113  * @param newrank : new root rank
114  * @return Whether the update succeeded (fails if another root update/union has been perfomed in the
115  * interim)
116  */
117  bool updateRoot(const parent_t x, const rank_t oldrank, const parent_t y, const rank_t newrank) {
118  block_t oldState = get(x);
119  parent_t nextN = b2p(oldState);
120  rank_t rankN = b2r(oldState);
121 
122  if (nextN != x || rankN != oldrank) return false;
123  // set the parent and rank of the new record
124  block_t newVal = pr2b(y, newrank);
125 
126  return this->get(x).compare_exchange_strong(oldState, newVal);
127  }
128 
129 public:
130  /**
131  * Clears the DisjointSet of all nodes
132  * Invalidates all iterators
133  */
134  void clear() {
135  a_blocks.clear();
136  }
137 
138  /**
139  * Check whether the two indices are in the same set
140  * @param x node to be checked
141  * @param y node to be checked
142  * @return where the two indices are in the same set
143  */
144  bool sameSet(parent_t x, parent_t y) {
145  while (true) {
146  x = findNode(x);
147  y = findNode(y);
148  if (x == y) return true;
149  // if x's parent is itself, they are not the same set
150  if (b2p(get(x)) == x) return false;
151  }
152  }
153 
154  /**
155  * Union the two specified index nodes
156  * @param x node to be unioned
157  * @param y node to be unioned
158  */
159  void unionNodes(parent_t x, parent_t y) {
160  while (true) {
161  x = findNode(x);
162  y = findNode(y);
163 
164  // no need to union if both already in same set
165  if (x == y) return;
166 
167  rank_t xrank = b2r(get(x));
168  rank_t yrank = b2r(get(y));
169 
170  // if x comes before y (better rank or earlier & equal node)
171  if (xrank > yrank || ((xrank == yrank) && x > y)) {
172  std::swap(x, y);
173  std::swap(xrank, yrank);
174  }
175  // join the trees together
176  // perhaps we can optimise the use of compare_exchange_strong here, as we're in a pessimistic loop
177  if (!updateRoot(x, xrank, y, yrank)) {
178  continue;
179  }
180  // make sure that the ranks are orderable
181  if (xrank == yrank) {
182  updateRoot(y, yrank, y, yrank + 1);
183  }
184  break;
185  }
186  }
187 
188  /**
189  * Create a node with its parent as itself, rank 0
190  * @return the newly created block
191  */
192  inline block_t makeNode() {
193  // make node and find out where we've added it
194  size_t nodeDetails = a_blocks.createNode();
195 
196  a_blocks.get(nodeDetails).store(pr2b(nodeDetails, 0));
197 
198  return a_blocks.get(nodeDetails).load();
199  };
200 
201  /**
202  * Extract parent from block
203  * @param inblock the block to be masked
204  * @return The parent_t contained in the upper half of block_t
205  */
206  static inline parent_t b2p(const block_t inblock) {
207  return (parent_t)(inblock >> split_size);
208  };
209 
210  /**
211  * Extract rank from block
212  * @param inblock the block to be masked
213  * @return the rank_t contained in the lower half of block_t
214  */
215  static inline rank_t b2r(const block_t inblock) {
216  return (rank_t)(inblock & rank_mask);
217  };
218 
219  /**
220  * Yield a block given parent and rank
221  * @param parent the top half bits
222  * @param rank the lower half bits
223  * @return the resultant block after merge
224  */
225  static inline block_t pr2b(const parent_t parent, const rank_t rank) {
226  return (((block_t)parent) << split_size) | rank;
227  };
228 };
229 
230 template <typename StorePair>
232  int operator()(const StorePair& a, const StorePair& b) {
233  if (a.first < b.first) {
234  return -1;
235  } else if (b.first < a.first) {
236  return 1;
237  } else {
238  return 0;
239  }
240  }
241 
242  bool less(const StorePair& a, const StorePair& b) {
243  return operator()(a, b) < 0;
244  }
245 
246  bool equal(const StorePair& a, const StorePair& b) {
247  return operator()(a, b) == 0;
248  }
249 };
250 
251 template <typename SparseDomain>
253  DisjointSet ds;
254 
255  template <typename TupleType>
256  friend class EquivalenceRelation;
257 
258  using PairStore = std::pair<SparseDomain, parent_t>;
259  using SparseMap =
262 
264 
266  // mapping from union-find val to souffle, union-find encoded as index
268 
269 public:
270  /**
271  * Retrieve dense encoding, adding it in if non-existent
272  * @param in the sparse value
273  * @return the corresponding dense value
274  */
275  parent_t toDense(const SparseDomain in) {
276  // insert into the mapping - if the key doesn't exist (in), the function will be called
277  // and a dense value will be created for it
278  PairStore p = {in, -1};
279  return sparseToDenseMap.insert(p, [&](PairStore& p) {
280  parent_t c2 = DisjointSet::b2p(this->ds.makeNode());
281  this->denseToSparseMap.insertAt(c2, p.first);
282  p.second = c2;
283  return c2;
284  });
285  }
286 
287 public:
288  SparseDisjointSet() = default;
289 
290  // copy ctor
291  SparseDisjointSet(SparseDisjointSet& other) = delete;
292 
293  // move ctor
294  SparseDisjointSet(SparseDisjointSet&& other) = delete;
295 
296  // copy assign ctor
298 
299  // move assign ctor
300  SparseDisjointSet& operator=(SparseDisjointSet&& other) = delete;
301 
302  /**
303  * For the given dense value, return the associated sparse value
304  * Undefined behaviour if dense value not in set
305  * @param in the supplied dense value
306  * @return the sparse value from the denseToSparseMap
307  */
308  inline const SparseDomain toSparse(const parent_t in) const {
309  return denseToSparseMap.get(in);
310  };
311 
312  /* a wrapper to enable checking in the sparse set - however also adds them if not already existing */
313  inline bool sameSet(SparseDomain x, SparseDomain y) {
314  return ds.sameSet(toDense(x), toDense(y));
315  };
316  /* finds the node in the underlying disjoint set, adding the node if non-existent */
317  inline SparseDomain findNode(SparseDomain x) {
318  return toSparse(ds.findNode(toDense(x)));
319  };
320  /* union the nodes, add if not existing */
321  inline void unionNodes(SparseDomain x, SparseDomain y) {
322  ds.unionNodes(toDense(x), toDense(y));
323  };
324 
325  inline std::size_t size() {
326  return ds.size();
327  };
328 
329  /**
330  * Remove all elements from this disjoint set
331  */
332  void clear() {
333  ds.clear();
336  }
337 
338  /* wrapper for node creation */
339  inline void makeNode(SparseDomain val) {
340  // dense has the behaviour of creating if not exists.
341  toDense(val);
342  };
343 
344  /* whether we the supplied node exists */
345  inline bool nodeExists(const SparseDomain val) const {
346  return sparseToDenseMap.contains({val, -1});
347  };
348 
349  inline bool contains(SparseDomain v1, SparseDomain v2) {
350  if (nodeExists(v1) && nodeExists(v2)) {
351  return sameSet(v1, v2);
352  }
353  return false;
354  }
355 };
356 } // namespace souffle
souffle::PiggyList::get
T & get(size_t index) const
Retrieve a reference to the stored value at index.
Definition: PiggyList.h:235
souffle::SparseDisjointSet::sameSet
bool sameSet(SparseDomain x, SparseDomain y)
Definition: UnionFind.h:319
souffle::parent_t
uint64_t parent_t
Definition: UnionFind.h:41
souffle::DisjointSet::DisjointSet
DisjointSet()=default
souffle::PiggyList::size
size_t size() const
Well, returns the number of nodes exist within the list + number of nodes queued to be inserted The r...
Definition: PiggyList.h:181
souffle::SparseDisjointSet
Definition: UnionFind.h:258
souffle::detail::btree::clear
void clear()
Clears this tree.
Definition: BTree.h:1948
souffle::DisjointSet::clear
void clear()
Clears the DisjointSet of all nodes Invalidates all iterators.
Definition: UnionFind.h:140
souffle::RandomInsertPiggyList::insertAt
void insertAt(size_t index, T value)
Definition: PiggyList.h:90
souffle::SparseDisjointSet::PairStore
std::pair< SparseDomain, parent_t > PairStore
Definition: UnionFind.h:264
souffle::RandomInsertPiggyList::get
T & get(size_t index) const
Definition: PiggyList.h:83
souffle::SparseDisjointSet::size
std::size_t size()
Definition: UnionFind.h:331
souffle::rank_mask
constexpr block_t rank_mask
Definition: UnionFind.h:49
souffle::DisjointSet::findNode
parent_t findNode(parent_t x)
Equivalent to the find() function in union/find Find the highest ancestor of the provided node - flat...
Definition: UnionFind.h:97
souffle::SparseDisjointSet::toDense
parent_t toDense(const SparseDomain in)
Retrieve dense encoding, adding it in if non-existent.
Definition: UnionFind.h:281
souffle::DisjointSet::sameSet
bool sameSet(parent_t x, parent_t y)
Check whether the two indices are in the same set.
Definition: UnionFind.h:150
souffle::SparseDisjointSet::unionNodes
void unionNodes(SparseDomain x, SparseDomain y)
Definition: UnionFind.h:327
souffle::SparseDisjointSet::denseToSparseMap
DenseMap denseToSparseMap
Definition: UnionFind.h:273
souffle::EqrelMapComparator::less
bool less(const StorePair &a, const StorePair &b)
Definition: UnionFind.h:248
souffle::DisjointSet::makeNode
block_t makeNode()
Create a node with its parent as itself, rank 0.
Definition: UnionFind.h:198
souffle::PiggyList::createNode
size_t createNode()
Definition: PiggyList.h:210
souffle::detail::LambdaBTree::insert
Functor::result_type insert(Key &k, const Functor &f)
Inserts the given key into this tree.
Definition: LambdaBTree.h:87
souffle::SparseDisjointSet::contains
bool contains(SparseDomain v1, SparseDomain v2)
Definition: UnionFind.h:355
LambdaBTree.h
souffle::DisjointSet::pr2b
static block_t pr2b(const parent_t parent, const rank_t rank)
Yield a block given parent and rank.
Definition: UnionFind.h:231
souffle::SparseDisjointSet::findNode
SparseDomain findNode(SparseDomain x)
Definition: UnionFind.h:323
souffle::PiggyList
Definition: PiggyList.h:143
souffle::EqrelMapComparator::operator()
int operator()(const StorePair &a, const StorePair &b)
Definition: UnionFind.h:238
souffle::PiggyList::clear
void clear()
Clear all elements from the PiggyList.
Definition: PiggyList.h:246
souffle::block_t
uint64_t block_t
Definition: UnionFind.h:47
souffle::SparseDisjointSet::makeNode
void makeNode(SparseDomain val)
Definition: UnionFind.h:345
souffle::SparseDisjointSet::toSparse
const SparseDomain toSparse(const parent_t in) const
For the given dense value, return the associated sparse value Undefined behaviour if dense value not ...
Definition: UnionFind.h:314
souffle::SparseDisjointSet::clear
void clear()
Remove all elements from this disjoint set.
Definition: UnionFind.h:338
PiggyList.h
souffle::SparseDisjointSet::SparseDisjointSet
SparseDisjointSet()=default
souffle::EqrelMapComparator
Definition: UnionFind.h:237
souffle::DisjointSet::unionNodes
void unionNodes(parent_t x, parent_t y)
Union the two specified index nodes.
Definition: UnionFind.h:165
souffle::RandomInsertPiggyList::clear
void clear()
Definition: PiggyList.h:110
souffle::DisjointSet::updateRoot
bool updateRoot(const parent_t x, const rank_t oldrank, const parent_t y, const rank_t newrank)
Update the root of the tree of which x is, to have y as the base instead.
Definition: UnionFind.h:123
souffle::SparseDisjointSet::sparseToDenseMap
SparseMap sparseToDenseMap
Definition: UnionFind.h:271
souffle::DisjointSet::get
std::atomic< block_t > & get(parent_t node) const
Yield reference to the node by its node index.
Definition: UnionFind.h:86
souffle::DisjointSet::operator=
DisjointSet & operator=(DisjointSet &ds)=delete
b
l j a showGridBackground &&c b raw series this eventEmitter b
Definition: htmlJsChartistMin.h:15
souffle::SparseDisjointSet::operator=
SparseDisjointSet & operator=(SparseDisjointSet &other)=delete
souffle::detail::btree::contains
bool contains(const Key &k) const
Determines whether the given element is a member of this tree.
Definition: BTree.h:1756
souffle::DisjointSet::b2p
static parent_t b2p(const block_t inblock)
Extract parent from block.
Definition: UnionFind.h:212
souffle::rank_t
uint8_t rank_t
Definition: UnionFind.h:39
souffle::DisjointSet::b2r
static rank_t b2r(const block_t inblock)
Extract rank from block.
Definition: UnionFind.h:221
souffle
Definition: AggregateOp.h:25
souffle::RandomInsertPiggyList< value_type >
souffle::DisjointSet
Structure that emulates a Disjoint Set, i.e.
Definition: UnionFind.h:54
souffle::split_size
constexpr uint8_t split_size
Definition: UnionFind.h:44
souffle::detail::btree< PairStore, EqrelMapComparator< PairStore >, std::allocator< PairStore >, blockSize, typename detail::default_strategy< PairStore >::type, isSet, EqrelMapComparator< PairStore >, detail::updater< PairStore > >::operation_hints
btree_operation_hints< 1 > operation_hints
Definition: BTree.h:1231
souffle::SparseDisjointSet::nodeExists
bool nodeExists(const SparseDomain val) const
Definition: UnionFind.h:351
souffle::LambdaBTreeSet< PairStore, std::function< parent_t(PairStore &)>, EqrelMapComparator< PairStore > >
souffle::SparseDisjointSet::last_ins
SparseMap::operation_hints last_ins
Definition: UnionFind.h:269
souffle::EquivalenceRelation
Definition: EquivalenceRelation.h:50
souffle::DisjointSet::size
size_t size()
Return the number of elements in this disjoint set (not the number of pairs)
Definition: UnionFind.h:76
souffle::DisjointSet::a_blocks
PiggyList< std::atomic< block_t > > a_blocks
Definition: UnionFind.h:58
souffle::EqrelMapComparator::equal
bool equal(const StorePair &a, const StorePair &b)
Definition: UnionFind.h:252
p
a horizontalBars(j=m=void 0===a.axisX.type?new c.AutoScaleAxis(c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX,{highLow:d, referenceValue:0})):a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX,{highLow:d, referenceValue:0})), l=n=void 0===a.axisY.type?new c.StepAxis(c.Axis.units.y, b.normalized.series, o,{ticks:k}):a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, a.axisY)) var p
Definition: htmlJsChartistMin.h:15
souffle::SparseDisjointSet::ds
DisjointSet ds
Definition: UnionFind.h:259