Lagrange
fast_edge_sort.h
1/*
2 * Copyright 2024 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13#pragma once
14
15#include <lagrange/utils/assert.h>
16#include <lagrange/utils/timing.h>
17
18#include <tbb/parallel_sort.h>
19
20#include <algorithm>
21#include <numeric>
22
23namespace lagrange::internal {
24
25template <typename Index>
27{
28 Index v1;
29 Index v2;
30 Index id;
31
32 UnorientedEdge(Index x, Index y, Index c)
33 : v1(std::min(x, y))
34 , v2(std::max(x, y))
35 , id(c)
36 {}
37
38 auto key() const { return std::make_pair(v1, v2); }
39
40 bool operator<(const UnorientedEdge& e) const { return key() < e.key(); }
41
42 bool operator!=(const UnorientedEdge& e) const { return key() != e.key(); }
43};
44
66template <typename Index, typename Func>
67std::vector<Index> fast_edge_sort(
68 Index num_edges,
69 Index num_vertices,
70 Func get_edge,
71 span<Index> vertex_to_first_edge = {})
72{
73 std::vector<Index> local_buffer;
74 if (vertex_to_first_edge.empty()) {
75 local_buffer.assign(num_vertices + 1, 0);
76 vertex_to_first_edge = local_buffer;
77 } else {
78 std::fill(vertex_to_first_edge.begin(), vertex_to_first_edge.end(), Index(0));
79 }
80 la_runtime_assert(vertex_to_first_edge.size() == static_cast<size_t>(num_vertices) + 1);
81 // Count number of edges starting at each vertex
82 for (Index e = 0; e < num_edges; ++e) {
83 std::array<Index, 2> v = get_edge(e);
84 if (v[0] > v[1]) {
85 std::swap(v[0], v[1]);
86 }
87 vertex_to_first_edge[v[0] + 1]++;
88 }
89 // Prefix sum to compute actual offsets
90 std::partial_sum(
91 vertex_to_first_edge.begin(),
92 vertex_to_first_edge.end(),
93 vertex_to_first_edge.begin());
94 la_runtime_assert(vertex_to_first_edge.back() == num_edges);
95 // Bucket each edge id to its respective starting vertex
96 std::vector<Index> edge_ids(num_edges);
97 for (Index e = 0; e < num_edges; ++e) {
98 std::array<Index, 2> v = get_edge(e);
99 if (v[0] > v[1]) {
100 std::swap(v[0], v[1]);
101 }
102 edge_ids[vertex_to_first_edge[v[0]]++] = e;
103 }
104 // Shift back the offset buffer 'vertex_to_first_edge' (can use std::shift_right in C++20 :p)
105 std::rotate(
106 vertex_to_first_edge.rbegin(),
107 vertex_to_first_edge.rbegin() + 1,
108 vertex_to_first_edge.rend());
109 vertex_to_first_edge.front() = 0;
110 // Sort each bucket in parallel
111 tbb::parallel_for(Index(0), num_vertices, [&](Index v) {
112 tbb::parallel_sort(
113 edge_ids.begin() + vertex_to_first_edge[v],
114 edge_ids.begin() + vertex_to_first_edge[v + 1],
115 [&](Index ei, Index ej) {
116 auto vi = get_edge(ei);
117 auto vj = get_edge(ej);
118 if (vi[0] > vi[1]) std::swap(vi[0], vi[1]);
119 if (vj[0] > vj[1]) std::swap(vj[0], vj[1]);
120 return vi < vj;
121 });
122 });
123 return edge_ids;
124}
125
126} // namespace lagrange::internal
#define la_runtime_assert(...)
Runtime assertion check.
Definition: assert.h:169
constexpr void swap(function_ref< R(Args...)> &lhs, function_ref< R(Args...)> &rhs) noexcept
Swaps the referred callables of lhs and rhs.
Definition: function_ref.h:114
::nonstd::span< T, Extent > span
A bounds-safe view for sequences of objects.
Definition: span.h:27
nullptr_t, size_t, ptrdiff_t basic_ostream bad_weak_ptr extent, remove_extent, is_array,...
Definition: attribute_string_utils.h:21
std::vector< Index > fast_edge_sort(Index num_edges, Index num_vertices, Func get_edge, span< Index > vertex_to_first_edge={})
Sort an array of edges using a parallel bucket sort.
Definition: fast_edge_sort.h:67
Definition: fast_edge_sort.h:27