Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
reinsertion_optimizer.h
Go to the documentation of this file.
1#ifndef BVH_V2_REINSERTION_OPTIMIZER_H
2#define BVH_V2_REINSERTION_OPTIMIZER_H
3
4#include "bvh/v2/bvh.h"
6#include "bvh/v2/executor.h"
7
8#include <vector>
9#include <algorithm>
10
11namespace bvh::v2 {
12
13template <typename Node>
15 using Scalar = typename Node::Scalar;
17
18public:
19 struct Config {
20 /// Fraction of the number of nodes to optimize per iteration.
21 Scalar batch_size_ratio = static_cast<Scalar>(0.05);
22
23 /// Maximum number of iterations.
24 size_t max_iter_count = 3;
25 };
26
27 static void optimize(ThreadPool& thread_pool, Bvh<Node>& bvh, const Config& config = {}) {
28 ParallelExecutor executor(thread_pool);
29 optimize(executor, bvh, config);
30 }
31
32 static void optimize(Bvh<Node>& bvh, const Config& config = {}) {
33 SequentialExecutor executor;
34 optimize(executor, bvh, config);
35 }
36
37private:
38 struct Candidate {
39 size_t node_id = 0;
40 Scalar cost = -std::numeric_limits<Scalar>::max();
41
42 BVH_ALWAYS_INLINE bool operator > (const Candidate& other) const {
43 return cost > other.cost;
44 }
45 };
46
47 struct Reinsertion {
48 size_t from = 0;
49 size_t to = 0;
50 Scalar area_diff = static_cast<Scalar>(0);
51
52 BVH_ALWAYS_INLINE bool operator > (const Reinsertion& other) const {
53 return area_diff > other.area_diff;
54 }
55 };
56
58 std::vector<size_t> parents_;
59
60 ReinsertionOptimizer(Bvh<Node>& bvh, std::vector<size_t>&& parents)
61 : bvh_(bvh)
62 , parents_(std::move(parents))
63 {}
64
65 template <typename Derived>
66 static void optimize(Executor<Derived>& executor, Bvh<Node>& bvh, const Config& config) {
67 auto parents = compute_parents(executor, bvh);
68 ReinsertionOptimizer<Node>(bvh, std::move(parents)).optimize(executor, config);
69 }
70
71 template <typename Derived>
72 static std::vector<size_t> compute_parents(Executor<Derived>& executor, const Bvh<Node>& bvh) {
73 std::vector<size_t> parents(bvh.nodes.size());
74 parents[0] = 0;
75 executor.for_each(0, bvh.nodes.size(),
76 [&] (size_t begin, size_t end) {
77 for (size_t i = begin; i < end; ++i) {
78 auto& node = bvh.nodes[i];
79 if (!node.is_leaf()) {
80 parents[node.index.first_id() + 0] = i;
81 parents[node.index.first_id() + 1] = i;
82 }
83 }
84 });
85 return parents;
86 }
87
88 BVH_ALWAYS_INLINE std::vector<Candidate> find_candidates(size_t target_count) {
89 // Gather the `target_count` nodes that have the highest cost.
90 // Note that this may produce fewer nodes if the BVH has fewer than `target_count` nodes.
91 const auto node_count = std::min(bvh_.nodes.size(), target_count + 1);
92 std::vector<Candidate> candidates;
93 for (size_t i = 1; i < node_count; ++i)
94 candidates.push_back(Candidate { i, bvh_.nodes[i].get_bbox().get_half_area() });
95 std::make_heap(candidates.begin(), candidates.end(), std::greater<>{});
96 for (size_t i = node_count; i < bvh_.nodes.size(); ++i) {
97 auto cost = bvh_.nodes[i].get_bbox().get_half_area();
98 if (candidates.front().cost < cost) {
99 std::pop_heap(candidates.begin(), candidates.end(), std::greater<>{});
100 candidates.back() = Candidate { i, cost };
101 std::push_heap(candidates.begin(), candidates.end(), std::greater<>{});
102 }
103 }
104 return candidates;
105 }
106
108 assert(node_id != 0);
109
110 /*
111 * Here is an example that explains how the cost of a reinsertion is computed. For the
112 * reinsertion from A to C, in the figure below, we need to remove P1, replace it by B,
113 * and create a node that holds A and C and place it where C was.
114 *
115 * R
116 * / \
117 * Pn Q1
118 * / \
119 * ... ...
120 * / \
121 * P1 C
122 * / \
123 * A B
124 *
125 * The resulting area *decrease* is (SA(x) means the surface area of x):
126 *
127 * SA(P1) + : P1 was removed
128 * SA(P2) - SA(B) + : P2 now only contains B
129 * SA(P3) - SA(B U sibling(P2)) + : Same but for P3
130 * ... +
131 * SA(Pn) - SA(B U sibling(P2) U ... U sibling(P(n - 1)) + : Same but for Pn
132 * 0 + : R does not change
133 * SA(Q1) - SA(Q1 U A) + : Q1 now contains A
134 * SA(Q2) - SA(Q2 U A) + : Q2 now contains A
135 * ... +
136 * -SA(A U C) : For the parent of A and C
137 */
138
139 Reinsertion best_reinsertion { /*.from */ node_id, 0, 0 };
140 auto node_area = bvh_.nodes[node_id].get_bbox().get_half_area();
141 auto parent_area = bvh_.nodes[parents_[node_id]].get_bbox().get_half_area();
142 auto area_diff = parent_area;
143 auto sibling_id = Bvh<Node>::get_sibling_id(node_id);
144 auto pivot_bbox = bvh_.nodes[sibling_id].get_bbox();
145 auto parent_id = parents_[node_id];
146 auto pivot_id = parent_id;
147
148 std::vector<std::pair<Scalar, size_t>> stack;
149 do {
150 // Try to find a reinsertion in the sibling at the current level of the tree
151 stack.emplace_back(area_diff, sibling_id);
152 while (!stack.empty()) {
153 auto top = stack.back();
154 stack.pop_back();
155 if (top.first - node_area <= best_reinsertion.area_diff)
156 continue;
157
158 auto& dst_node = bvh_.nodes[top.second];
159 auto merged_area = dst_node.get_bbox().extend(bvh_.nodes[node_id].get_bbox()).get_half_area();
160 auto reinsert_area = top.first - merged_area;
161 if (reinsert_area > best_reinsertion.area_diff) {
162 best_reinsertion.to = top.second;
163 best_reinsertion.area_diff = reinsert_area;
164 }
165
166 if (!dst_node.is_leaf()) {
167 auto child_area = reinsert_area + dst_node.get_bbox().get_half_area();
168 stack.emplace_back(child_area, dst_node.index.first_id() + 0);
169 stack.emplace_back(child_area, dst_node.index.first_id() + 1);
170 }
171 }
172
173 // Compute the bounding box on the path from the node to the root, and record the
174 // corresponding decrease in area.
175 if (pivot_id != parent_id) {
176 pivot_bbox.extend(bvh_.nodes[sibling_id].get_bbox());
177 area_diff += bvh_.nodes[pivot_id].get_bbox().get_half_area() - pivot_bbox.get_half_area();
178 }
179
180 sibling_id = Bvh<Node>::get_sibling_id(pivot_id);
181 pivot_id = parents_[pivot_id];
182 } while (pivot_id != 0);
183
184 if (best_reinsertion.to == Bvh<Node>::get_sibling_id(best_reinsertion.from) ||
185 best_reinsertion.to == parents_[best_reinsertion.from])
186 best_reinsertion = {};
187 return best_reinsertion;
188 }
189
190 BVH_ALWAYS_INLINE void reinsert_node(size_t from, size_t to) {
191 auto sibling_id = Bvh<Node>::get_sibling_id(from);
192 auto parent_id = parents_[from];
193 auto sibling_node = bvh_.nodes[sibling_id];
194 auto dst_node = bvh_.nodes[to];
195
196 bvh_.nodes[to].index = Node::Index::make_inner(Bvh<Node>::get_left_sibling_id(from));
197 bvh_.nodes[sibling_id] = dst_node;
198 bvh_.nodes[parent_id] = sibling_node;
199
200 if (!dst_node.is_leaf()) {
201 parents_[dst_node.index.first_id() + 0] = sibling_id;
202 parents_[dst_node.index.first_id() + 1] = sibling_id;
203 }
204 if (!sibling_node.is_leaf()) {
205 parents_[sibling_node.index.first_id() + 0] = parent_id;
206 parents_[sibling_node.index.first_id() + 1] = parent_id;
207 }
208
209 parents_[sibling_id] = to;
210 parents_[from] = to;
211 refit_from(to);
212 refit_from(parent_id);
213 }
214
216 do {
217 auto& node = bvh_.nodes[index];
218 if (!node.is_leaf()) {
219 node.set_bbox(
220 bvh_.nodes[node.index.first_id() + 0].get_bbox().extend(
221 bvh_.nodes[node.index.first_id() + 1].get_bbox()));
222 }
223 index = parents_[index];
224 } while (index != 0);
225 }
226
227 BVH_ALWAYS_INLINE auto get_conflicts(size_t from, size_t to) {
228 return std::array<size_t, 5> {
229 to, from,
231 parents_[to],
232 parents_[from]
233 };
234 }
235
236 template <typename Derived>
237 void optimize(Executor<Derived>& executor, const Config& config) {
238 auto batch_size = std::max(size_t{1},
239 static_cast<size_t>(static_cast<Scalar>(bvh_.nodes.size()) * config.batch_size_ratio));
240 std::vector<Reinsertion> reinsertions;
241 std::vector<bool> touched(bvh_.nodes.size());
242
243 for (size_t iter = 0; iter < config.max_iter_count; ++iter) {
244 auto candidates = find_candidates(batch_size);
245
246 std::fill(touched.begin(), touched.end(), false);
247 reinsertions.resize(candidates.size());
248 executor.for_each(0, candidates.size(),
249 [&] (size_t begin, size_t end) {
250 for (size_t i = begin; i < end; ++i)
251 reinsertions[i] = find_reinsertion(candidates[i].node_id);
252 });
253
254 reinsertions.erase(std::remove_if(reinsertions.begin(), reinsertions.end(),
255 [] (auto& r) { return r.area_diff <= 0; }), reinsertions.end());
256 std::sort(reinsertions.begin(), reinsertions.end(), std::greater<>{});
257
258 for (auto& reinsertion : reinsertions) {
259 auto conflicts = get_conflicts(reinsertion.from, reinsertion.to);
260 if (std::any_of(conflicts.begin(), conflicts.end(), [&] (size_t i) { return touched[i]; }))
261 continue;
262 for (auto conflict : conflicts)
263 touched[conflict] = true;
264 reinsert_node(reinsertion.from, reinsertion.to);
265 }
266 }
267 }
268};
269
270} // namespace bvh::v2
271
272#endif
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t r
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
static std::vector< size_t > compute_parents(Executor< Derived > &executor, const Bvh< Node > &bvh)
BVH_ALWAYS_INLINE std::vector< Candidate > find_candidates(size_t target_count)
Reinsertion find_reinsertion(size_t node_id)
BVH_ALWAYS_INLINE auto get_conflicts(size_t from, size_t to)
BVH_ALWAYS_INLINE void reinsert_node(size_t from, size_t to)
void optimize(Executor< Derived > &executor, const Config &config)
BVH_ALWAYS_INLINE void refit_from(size_t index)
static void optimize(Bvh< Node > &bvh, const Config &config={})
ReinsertionOptimizer(Bvh< Node > &bvh, std::vector< size_t > &&parents)
static void optimize(Executor< Derived > &executor, Bvh< Node > &bvh, const Config &config)
static void optimize(ThreadPool &thread_pool, Bvh< Node > &bvh, const Config &config={})
Definition bbox.h:9
Definition bbox.h:9
#define BVH_ALWAYS_INLINE
Definition platform.h:19
static BVH_ALWAYS_INLINE size_t get_sibling_id(size_t node_id)
Returns the index of a sibling of a node.
Definition bvh.h:43
Helper object that provides iteration and reduction over one-dimensional ranges.
Definition executor.h:14
void for_each(size_t begin, size_t end, const Loop &loop)
Definition executor.h:16
static BVH_ALWAYS_INLINE Index make_inner(size_t first_child)
Definition index.h:75
Executor that executes in parallel using the given thread pool.
Definition executor.h:42
BVH_ALWAYS_INLINE bool operator>(const Candidate &other) const
size_t max_iter_count
Maximum number of iterations.
Scalar batch_size_ratio
Fraction of the number of nodes to optimize per iteration.
BVH_ALWAYS_INLINE bool operator>(const Reinsertion &other) const
Executor that executes serially.
Definition executor.h:27