Wildmeshing Toolkit
Loading...
Searching...
No Matches
VertexColoring.hpp
1#pragma once
2
3#include <wmtk/threading/dynamic_parallel_for.hpp>
4#include <wmtk/threading/task_group.hpp>
5
6#include <algorithm>
7#include <atomic>
8#include <cassert>
9#include <cstddef>
10#include <cstdint>
11#include <exception>
12#include <memory>
13#include <mutex>
14#include <thread>
15#include <vector>
16
17namespace wmtk::utils {
18
40template <typename NeighborFn>
41std::vector<std::vector<size_t>> greedy_vertex_coloring(
42 const std::vector<size_t>& vids,
43 size_t vert_capacity,
44 int num_threads,
45 NeighborFn&& neighbors)
46{
47 constexpr uint32_t kNone = UINT32_MAX;
48 std::vector<uint32_t> slot(vert_capacity, kNone);
49 for (size_t i = 0; i < vids.size(); ++i) {
50 // A repeated vertex would not see its other copy as a neighbour and could land twice in
51 // one class, smoothed by two threads at once.
52 assert(slot[vids[i]] == kNone && "greedy_vertex_coloring: a vertex is listed twice");
53 slot[vids[i]] = uint32_t(i);
54 }
55
56 // Each entry is written by the one task that owns its index.
57 std::vector<std::vector<uint32_t>> adjacency(vids.size());
58 threading::dynamic_parallel_for(vids.size(), num_threads, 256, [&](size_t b, size_t e) {
59 std::vector<size_t> nb;
60 for (size_t i = b; i < e; ++i) {
61 nb.clear();
62 neighbors(vids[i], nb);
63 for (const size_t u : nb) {
64 if (u < slot.size() && slot[u] != kNone && slot[u] != i) {
65 adjacency[i].push_back(slot[u]);
66 }
67 }
68 }
69 });
70
71 // Greedy in `vids` order: the smallest color no already-colored neighbor holds.
72 std::vector<uint32_t> color(vids.size(), kNone);
73 std::vector<size_t> seen_at; // seen_at[c] == i + 1: a neighbor of vertex i holds color c
74 uint32_t n_colors = 0;
75 for (size_t i = 0; i < vids.size(); ++i) {
76 for (const uint32_t j : adjacency[i]) {
77 const uint32_t c = color[j];
78 if (c != kNone) {
79 seen_at[c] = i + 1;
80 }
81 }
82 uint32_t c = 0;
83 while (c < n_colors && seen_at[c] == i + 1) {
84 ++c;
85 }
86 if (c == n_colors) {
87 ++n_colors;
88 seen_at.push_back(0);
89 }
90 color[i] = c;
91 }
92
93 std::vector<std::vector<size_t>> classes(n_colors);
94 for (size_t i = 0; i < vids.size(); ++i) {
95 classes[color[i]].push_back(vids[i]);
96 }
97 return classes;
98}
99
109inline size_t color_class_chunk(size_t n, int num_threads, size_t max_chunk)
110{
111 const size_t per = n / (8 * size_t(num_threads > 0 ? num_threads : 1));
112 return per < 1 ? 1 : (per > max_chunk ? max_chunk : per);
113}
114
142template <typename F>
143size_t for_each_in_classes(
144 const std::vector<std::vector<size_t>>& classes,
145 int num_threads,
146 size_t max_chunk,
147 F&& fn)
148{
149 size_t n_total = 0;
150 for (const auto& cls : classes) n_total += cls.size();
151 if (n_total == 0) return 0;
152 const size_t nt = size_t(num_threads > 1 ? num_threads : 1);
153 if (nt == 1) {
154 size_t successes = 0;
155 for (const auto& cls : classes) {
156 for (const size_t v : cls) {
157 if (fn(v)) ++successes;
158 }
159 }
160 return successes;
161 }
162
163 // next[c]: the first vertex of class c not yet handed out.
164 auto next = std::make_unique<std::atomic<size_t>[]>(classes.size());
165 for (size_t c = 0; c < classes.size(); ++c) next[c].store(0, std::memory_order_relaxed);
166 // A reusable barrier: the last to arrive starts the next generation.
167 std::atomic<size_t> arrived{0};
168 std::atomic<size_t> generation{0};
169 const auto barrier = [&] {
170 const size_t gen = generation.load(std::memory_order_acquire);
171 if (arrived.fetch_add(1, std::memory_order_acq_rel) + 1 == nt) {
172 arrived.store(0, std::memory_order_relaxed);
173 generation.fetch_add(1, std::memory_order_release);
174 } else {
175 while (generation.load(std::memory_order_acquire) == gen) {
176 std::this_thread::yield();
177 }
178 }
179 };
180 std::atomic<size_t> successes{0};
181 std::atomic<bool> failed{false};
182 std::exception_ptr error;
183 std::mutex error_mutex;
184 // The start gate: kLaunching until every task is launched, then kGo -- or kAbort if a launch
185 // failed. Declared before the task_group, which waits for the tasks when it is destroyed.
186 enum : int { kLaunching = 0, kGo = 1, kAbort = 2 };
187 std::atomic<int> gate{kLaunching};
188
189 threading::task_group tg;
190 const auto task = [&] {
191 int state;
192 while ((state = gate.load(std::memory_order_acquire)) == kLaunching) {
193 std::this_thread::yield();
194 }
195 if (state == kAbort) return;
196 size_t mine = 0;
197 for (size_t c = 0; c < classes.size(); ++c) {
198 const std::vector<size_t>& cls = classes[c];
199 const size_t chunk = color_class_chunk(cls.size(), int(nt), max_chunk);
200 while (!failed.load(std::memory_order_relaxed)) {
201 const size_t b = next[c].fetch_add(chunk, std::memory_order_relaxed);
202 if (b >= cls.size()) break;
203 const size_t e = std::min(cls.size(), b + chunk);
204 try {
205 for (size_t i = b; i < e; ++i) {
206 if (fn(cls[i])) ++mine;
207 }
208 } catch (...) {
209 std::lock_guard<std::mutex> lock(error_mutex);
210 if (!error) error = std::current_exception();
211 failed.store(true, std::memory_order_relaxed);
212 }
213 }
214 // Not after the last class: tg.wait() is the barrier there.
215 if (c + 1 < classes.size()) barrier();
216 }
217 successes.fetch_add(mine, std::memory_order_relaxed);
218 };
219 try {
220 for (size_t t = 0; t < nt; ++t) {
221 tg.run(task);
222 }
223 } catch (...) {
224 gate.store(kAbort, std::memory_order_release);
225 throw;
226 }
227 gate.store(kGo, std::memory_order_release);
228 tg.wait();
229 if (error) std::rethrow_exception(error);
230 return successes.load();
231}
232
244template <typename Mesh>
245size_t
246smooth_classes_checked(Mesh& m, const std::vector<std::vector<size_t>>& classes, size_t max_chunk)
247{
248 struct Restore
249 {
250 bool& flag;
251 bool saved;
252 ~Restore() { flag = saved; }
253 } restore{m.m_check_smoothing_stays_in_star, m.m_check_smoothing_stays_in_star},
254 restore_pass{m.m_in_operation_pass, m.m_in_operation_pass};
255 m.m_check_smoothing_stays_in_star = true;
256 // A pass is running (see TetOptimizerMesh::m_in_operation_pass); this sweep does not go
257 // through run_pass().
258 m.m_in_operation_pass = true;
259 return for_each_in_classes(classes, m.NUM_THREADS, max_chunk, [&m](size_t v) {
260 return m.smooth_vertex(m.tuple_from_vertex(v));
261 });
262}
263
264} // namespace wmtk::utils