Skip to content

Commit 059c263

Browse files
Implement PQ and update KL
1 parent 68d3c1d commit 059c263

2 files changed

Lines changed: 311 additions & 83 deletions

File tree

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <cstddef>
5+
#include <functional>
6+
#include <limits>
7+
#include <unordered_map>
8+
#include <utility>
9+
#include <vector>
10+
11+
namespace bioimage_cpp::detail {
12+
13+
// Sentinel used by both locators below to mark "this key is not in the heap".
14+
inline constexpr std::size_t indexed_heap_not_in_heap =
15+
std::numeric_limits<std::size_t>::max();
16+
17+
// Locator concept used by IndexedHeap:
18+
// std::size_t at(key) const — returns position in heap, or `not_in_heap`.
19+
// void set(key, std::size_t pos) — records a key's position. `not_in_heap` clears it.
20+
21+
// Locator for keys drawn from [0, capacity). Backed by a flat vector so the
22+
// position lookup is a single load.
23+
class DenseLocator {
24+
public:
25+
static constexpr std::size_t not_in_heap = indexed_heap_not_in_heap;
26+
27+
explicit DenseLocator(const std::size_t capacity = 0)
28+
: positions_(capacity, not_in_heap) {}
29+
30+
[[nodiscard]] std::size_t at(const std::size_t key) const {
31+
return positions_[key];
32+
}
33+
34+
void set(const std::size_t key, const std::size_t pos) {
35+
positions_[key] = pos;
36+
}
37+
38+
// Resize and reset every position to `not_in_heap`. Use between solver
39+
// invocations on different graphs.
40+
void reset_capacity(const std::size_t capacity) {
41+
positions_.assign(capacity, not_in_heap);
42+
}
43+
44+
private:
45+
std::vector<std::size_t> positions_;
46+
};
47+
48+
// Locator for keys with sparse / arbitrary identity. Position lookup is a
49+
// hash-map probe.
50+
template <class KeyT, class Hash = std::hash<KeyT>>
51+
class SparseLocator {
52+
public:
53+
static constexpr std::size_t not_in_heap = indexed_heap_not_in_heap;
54+
55+
[[nodiscard]] std::size_t at(const KeyT &key) const {
56+
const auto it = positions_.find(key);
57+
return it == positions_.end() ? not_in_heap : it->second;
58+
}
59+
60+
void set(const KeyT &key, const std::size_t pos) {
61+
if (pos == not_in_heap) {
62+
positions_.erase(key);
63+
} else {
64+
positions_.insert_or_assign(key, pos);
65+
}
66+
}
67+
68+
void reserve(const std::size_t expected_keys) {
69+
positions_.reserve(expected_keys);
70+
}
71+
72+
void clear() {
73+
positions_.clear();
74+
}
75+
76+
private:
77+
std::unordered_map<KeyT, std::size_t, Hash> positions_;
78+
};
79+
80+
// Addressable max-heap with mutable priorities.
81+
//
82+
// Each key maps to exactly one entry in the heap, tracked via the Locator.
83+
// `push`, `change`, `push_or_change`, `erase`, and `pop` are O(log n) and keep
84+
// the locator in sync with the heap permutation. `top`, `contains`, and
85+
// `priority_of` are O(1). Compared to a `std::priority_queue` with lazy
86+
// invalidation, the heap never carries stale entries — its size equals the
87+
// number of currently active keys.
88+
//
89+
// The comparator `Compare` follows the standard "less-than → max-heap"
90+
// convention used by `std::priority_queue`. Specialize with `std::greater<>`
91+
// for a min-heap.
92+
template <
93+
class KeyT,
94+
class PriorityT,
95+
class Locator,
96+
class Compare = std::less<PriorityT>
97+
>
98+
class IndexedHeap {
99+
public:
100+
using key_type = KeyT;
101+
using priority_type = PriorityT;
102+
103+
struct Entry {
104+
KeyT key;
105+
PriorityT priority;
106+
};
107+
108+
static constexpr std::size_t not_in_heap = Locator::not_in_heap;
109+
110+
IndexedHeap() = default;
111+
explicit IndexedHeap(Locator locator) : locator_(std::move(locator)) {}
112+
113+
[[nodiscard]] std::size_t size() const { return heap_.size(); }
114+
[[nodiscard]] bool empty() const { return heap_.empty(); }
115+
116+
[[nodiscard]] bool contains(const KeyT &key) const {
117+
return locator_.at(key) != not_in_heap;
118+
}
119+
120+
[[nodiscard]] const PriorityT &priority_of(const KeyT &key) const {
121+
return heap_[locator_.at(key)].priority;
122+
}
123+
124+
[[nodiscard]] const Entry &top() const {
125+
return heap_.front();
126+
}
127+
128+
// Precondition: `key` is not currently in the heap.
129+
void push(KeyT key, PriorityT priority) {
130+
const auto pos = heap_.size();
131+
heap_.push_back({std::move(key), std::move(priority)});
132+
locator_.set(heap_.back().key, pos);
133+
sift_up(pos);
134+
}
135+
136+
// Precondition: `key` is currently in the heap.
137+
void change(const KeyT &key, PriorityT priority) {
138+
const auto pos = locator_.at(key);
139+
apply_change(pos, std::move(priority));
140+
}
141+
142+
void push_or_change(KeyT key, PriorityT priority) {
143+
const auto pos = locator_.at(key);
144+
if (pos == not_in_heap) {
145+
push(std::move(key), std::move(priority));
146+
} else {
147+
apply_change(pos, std::move(priority));
148+
}
149+
}
150+
151+
// No-op when the key is not in the heap. This is the "remove if exists"
152+
// semantic every caller needs.
153+
void erase(const KeyT &key) {
154+
const auto pos = locator_.at(key);
155+
if (pos != not_in_heap) {
156+
erase_at(pos);
157+
}
158+
}
159+
160+
Entry pop() {
161+
Entry top = heap_.front();
162+
erase_at(0);
163+
return top;
164+
}
165+
166+
void clear() {
167+
for (const auto &entry : heap_) {
168+
locator_.set(entry.key, not_in_heap);
169+
}
170+
heap_.clear();
171+
}
172+
173+
Locator &locator() { return locator_; }
174+
const Locator &locator() const { return locator_; }
175+
176+
private:
177+
std::vector<Entry> heap_;
178+
Locator locator_;
179+
Compare compare_;
180+
181+
void swap_positions(const std::size_t a, const std::size_t b) {
182+
std::swap(heap_[a], heap_[b]);
183+
locator_.set(heap_[a].key, a);
184+
locator_.set(heap_[b].key, b);
185+
}
186+
187+
void sift_up(std::size_t pos) {
188+
while (pos > 0) {
189+
const auto parent = (pos - 1) / 2;
190+
if (!compare_(heap_[parent].priority, heap_[pos].priority)) {
191+
break;
192+
}
193+
swap_positions(parent, pos);
194+
pos = parent;
195+
}
196+
}
197+
198+
void sift_down(std::size_t pos) {
199+
const auto n = heap_.size();
200+
while (true) {
201+
const auto left = 2 * pos + 1;
202+
if (left >= n) {
203+
break;
204+
}
205+
auto target = left;
206+
const auto right = left + 1;
207+
if (right < n && compare_(heap_[left].priority, heap_[right].priority)) {
208+
target = right;
209+
}
210+
if (!compare_(heap_[pos].priority, heap_[target].priority)) {
211+
break;
212+
}
213+
swap_positions(pos, target);
214+
pos = target;
215+
}
216+
}
217+
218+
void apply_change(const std::size_t pos, PriorityT new_priority) {
219+
const bool increased = compare_(heap_[pos].priority, new_priority);
220+
heap_[pos].priority = std::move(new_priority);
221+
if (increased) {
222+
sift_up(pos);
223+
} else {
224+
sift_down(pos);
225+
}
226+
}
227+
228+
void erase_at(const std::size_t pos) {
229+
const auto last = heap_.size() - 1;
230+
locator_.set(heap_[pos].key, not_in_heap);
231+
if (pos != last) {
232+
heap_[pos] = std::move(heap_[last]);
233+
locator_.set(heap_[pos].key, pos);
234+
heap_.pop_back();
235+
// The replacement could need sifting in either direction.
236+
sift_up(pos);
237+
sift_down(pos);
238+
} else {
239+
heap_.pop_back();
240+
}
241+
}
242+
};
243+
244+
template <class PriorityT, class Compare = std::less<PriorityT>>
245+
class DenseIndexedHeap final
246+
: public IndexedHeap<std::size_t, PriorityT, DenseLocator, Compare> {
247+
using base = IndexedHeap<std::size_t, PriorityT, DenseLocator, Compare>;
248+
249+
public:
250+
DenseIndexedHeap() = default;
251+
explicit DenseIndexedHeap(const std::size_t capacity)
252+
: base(DenseLocator(capacity)) {}
253+
254+
void reset_capacity(const std::size_t capacity) {
255+
this->clear();
256+
this->locator().reset_capacity(capacity);
257+
}
258+
};
259+
260+
template <
261+
class KeyT,
262+
class PriorityT,
263+
class Hash = std::hash<KeyT>,
264+
class Compare = std::less<PriorityT>
265+
>
266+
class SparseIndexedHeap final
267+
: public IndexedHeap<KeyT, PriorityT, SparseLocator<KeyT, Hash>, Compare> {
268+
using base = IndexedHeap<KeyT, PriorityT, SparseLocator<KeyT, Hash>, Compare>;
269+
270+
public:
271+
SparseIndexedHeap() = default;
272+
273+
void reserve(const std::size_t expected_keys) {
274+
this->locator().reserve(expected_keys);
275+
}
276+
};
277+
278+
} // namespace bioimage_cpp::detail

0 commit comments

Comments
 (0)