OpenPFC  0.1.4
Phase Field Crystal simulation framework
Loading...
Searching...
No Matches
sparse_vector.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2025 VTT Technical Research Centre of Finland Ltd
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
42#pragma once
43
44#include <algorithm>
45#include <cstddef>
46#include <vector>
47
50
51namespace pfc {
52namespace core {
53
54// Alias for backward compatibility
55using HostTag = backend::CpuTag;
56
66template <typename BackendTag, typename T> class SparseVector {
67private:
68 DataBuffer<BackendTag, size_t> m_indices; // Sorted indices
69 DataBuffer<BackendTag, T> m_data; // Values at those indices
70 size_t m_size;
71 bool m_indices_sorted;
72
76 void copy_indices_to_device(const std::vector<size_t> &sorted_indices) {
77 if constexpr (std::is_same_v<BackendTag, backend::CpuTag>) {
78 // CPU: Direct copy
79 std::copy(sorted_indices.begin(), sorted_indices.end(), m_indices.data());
80 }
81#if defined(OpenPFC_ENABLE_CUDA)
82 else if constexpr (std::is_same_v<BackendTag, backend::CudaTag>) {
83 // CUDA: Use cudaMemcpy
84 cudaError_t err = cudaMemcpy(m_indices.data(), sorted_indices.data(),
85 m_size * sizeof(size_t), cudaMemcpyHostToDevice);
86 if (err != cudaSuccess) {
87 throw std::runtime_error("CUDA copy failed: " +
88 std::string(cudaGetErrorString(err)));
89 }
90 }
91#endif
92 }
93
94public:
99 explicit SparseVector(size_t size)
100 : m_indices(size), m_data(size), m_size(size), m_indices_sorted(false) {}
101
106 explicit SparseVector(const std::vector<size_t> &indices)
107 : m_size(indices.size()), m_indices_sorted(false) {
108 if (m_size == 0) {
109 m_indices = DataBuffer<BackendTag, size_t>(0);
110 m_data = DataBuffer<BackendTag, T>(0);
111 m_indices_sorted = true;
112 return;
113 }
114
115 // Sort indices for optimal memory access
116 std::vector<size_t> sorted_indices = indices;
117 std::sort(sorted_indices.begin(), sorted_indices.end());
118 m_indices_sorted = true;
119
120 // Allocate buffers
121 m_indices = DataBuffer<BackendTag, size_t>(m_size);
122 m_data = DataBuffer<BackendTag, T>(m_size);
123
124 // Copy sorted indices to device
125 copy_indices_to_device(sorted_indices);
126 }
127
133 SparseVector(const std::vector<size_t> &indices, const std::vector<T> &data)
134 : m_size(indices.size()), m_indices_sorted(false) {
135 if (indices.size() != data.size()) {
136 throw std::runtime_error("SparseVector: indices and data size mismatch");
137 }
138
139 if (m_size == 0) {
140 m_indices = DataBuffer<BackendTag, size_t>(0);
141 m_data = DataBuffer<BackendTag, T>(0);
142 m_indices_sorted = true;
143 return;
144 }
145
146 // Sort indices and reorder data accordingly
147 std::vector<std::pair<size_t, T>> pairs;
148 pairs.reserve(m_size);
149 for (size_t i = 0; i < m_size; ++i) {
150 pairs.emplace_back(indices[i], data[i]);
151 }
152 std::sort(pairs.begin(), pairs.end(),
153 [](const auto &a, const auto &b) { return a.first < b.first; });
154
155 std::vector<size_t> sorted_indices;
156 std::vector<T> sorted_data;
157 sorted_indices.reserve(m_size);
158 sorted_data.reserve(m_size);
159 for (const auto &[idx, val] : pairs) {
160 sorted_indices.push_back(idx);
161 sorted_data.push_back(val);
162 }
163 m_indices_sorted = true;
164
165 // Allocate buffers
166 m_indices = DataBuffer<BackendTag, size_t>(m_size);
167 m_data = DataBuffer<BackendTag, T>(m_size);
168
169 // Copy to device
170 copy_indices_to_device(sorted_indices);
171 copy_data_to_device(sorted_data);
172 }
173
177 size_t size() const { return m_size; }
178
182 bool empty() const { return m_size == 0; }
183
187 const DataBuffer<BackendTag, size_t> &indices() const { return m_indices; }
188
192 DataBuffer<BackendTag, T> &data() { return m_data; }
193 const DataBuffer<BackendTag, T> &data() const { return m_data; }
194
198 bool is_sorted() const { return m_indices_sorted; }
199
200private:
204 void copy_data_to_device(const std::vector<T> &host_data) {
205 if constexpr (std::is_same_v<BackendTag, backend::CpuTag>) {
206 // CPU: Direct copy
207 std::copy(host_data.begin(), host_data.end(), m_data.data());
208 }
209#if defined(OpenPFC_ENABLE_CUDA)
210 else if constexpr (std::is_same_v<BackendTag, backend::CudaTag>) {
211 // CUDA: Use cudaMemcpy
212 cudaError_t err = cudaMemcpy(m_data.data(), host_data.data(),
213 m_size * sizeof(T), cudaMemcpyHostToDevice);
214 if (err != cudaSuccess) {
215 throw std::runtime_error("CUDA copy failed: " +
216 std::string(cudaGetErrorString(err)));
217 }
218 }
219#endif
220 }
221};
222
226template <typename BackendTag, typename T>
228 (void)vec; // Unused parameter
229 return std::is_same_v<BackendTag, backend::CpuTag>;
230}
231
232} // namespace core
233
234// Convenience namespace for backward compatibility with tests
235namespace sparsevector {
236
237// Alias for backward compatibility
238using HostTag = backend::CpuTag;
239
243template <typename T, typename BackendTag = backend::CpuTag>
244core::SparseVector<BackendTag, T> create(size_t size) {
245 return core::SparseVector<BackendTag, T>(size);
246}
247
251template <typename T, typename BackendTag = backend::CpuTag>
252core::SparseVector<BackendTag, T> create(std::initializer_list<size_t> indices) {
253 return core::SparseVector<BackendTag, T>(std::vector<size_t>(indices));
254}
255
259template <typename T, typename BackendTag = backend::CpuTag>
260core::SparseVector<BackendTag, T> create(const std::vector<size_t> &indices) {
261 return core::SparseVector<BackendTag, T>(indices);
262}
263
267template <typename T, typename BackendTag = backend::CpuTag>
268core::SparseVector<BackendTag, T> create(const std::vector<size_t> &indices,
269 const std::vector<T> &data) {
270 return core::SparseVector<BackendTag, T>(indices, data);
271}
272
276template <typename BackendTag, typename T>
277size_t get_size(const core::SparseVector<BackendTag, T> &vec) {
278 return vec.size();
279}
280
287template <typename BackendTag, typename T>
288void set_index(core::SparseVector<BackendTag, T> &vec,
289 const std::vector<size_t> &indices) {
290 static_assert(std::is_same_v<BackendTag, backend::CpuTag>,
291 "set_index only available for CPU");
292 // Recreate vector with new indices
293 // This is a workaround for testing - in production, create new SparseVector
294 core::SparseVector<BackendTag, T> new_vec(indices);
295 vec = std::move(new_vec);
296}
297
301template <typename BackendTag, typename T>
302void set_data(core::SparseVector<BackendTag, T> &vec, const std::vector<T> &data) {
303 static_assert(std::is_same_v<BackendTag, backend::CpuTag>,
304 "set_data only available for CPU");
305 if (data.size() != vec.size()) {
306 throw std::runtime_error("set_data: size mismatch");
307 }
308 // Direct copy to data buffer (mutable access)
309 T *data_ptr = vec.data().data();
310 std::copy(data.begin(), data.end(), data_ptr);
311}
312
316template <typename BackendTag, typename T>
317std::vector<size_t> get_index(const core::SparseVector<BackendTag, T> &vec) {
318 static_assert(std::is_same_v<BackendTag, backend::CpuTag>,
319 "get_index only available for CPU");
320 std::vector<size_t> result(vec.size());
321 std::copy(vec.indices().data(), vec.indices().data() + vec.size(), result.begin());
322 return result;
323}
324
328template <typename BackendTag, typename T>
329std::vector<T> get_data(const core::SparseVector<BackendTag, T> &vec) {
330 static_assert(std::is_same_v<BackendTag, backend::CpuTag>,
331 "get_data only available for CPU");
332 std::vector<T> result(vec.size());
333 std::copy(vec.data().data(), vec.data().data() + vec.size(), result.begin());
334 return result;
335}
336
340template <typename BackendTag, typename T>
341size_t get_index(const core::SparseVector<BackendTag, T> &vec, size_t i) {
342 static_assert(std::is_same_v<BackendTag, backend::CpuTag>,
343 "get_index only available for CPU");
344 return vec.indices().data()[i];
345}
346
350template <typename BackendTag, typename T>
351T get_data(const core::SparseVector<BackendTag, T> &vec, size_t i) {
352 static_assert(std::is_same_v<BackendTag, backend::CpuTag>,
353 "get_data only available for CPU");
354 return vec.data().data()[i];
355}
356
360template <typename BackendTag, typename T>
361std::pair<size_t, T> get_entry(const core::SparseVector<BackendTag, T> &vec,
362 size_t i) {
363 static_assert(std::is_same_v<BackendTag, backend::CpuTag>,
364 "get_entry only available for CPU");
365 return {vec.indices().data()[i], vec.data().data()[i]};
366}
367
368} // namespace sparsevector
369
370} // namespace pfc
Backend tags for compile-time backend selection.
Sparse vector for indexed data views.
Definition sparse_vector.hpp:66
size_t size() const
Get number of entries.
Definition sparse_vector.hpp:177
bool empty() const
Check if empty.
Definition sparse_vector.hpp:182
SparseVector(const std::vector< size_t > &indices, const std::vector< T > &data)
Construct SparseVector with indices and initial data.
Definition sparse_vector.hpp:133
SparseVector(const std::vector< size_t > &indices)
Construct SparseVector with given indices.
Definition sparse_vector.hpp:106
const DataBuffer< BackendTag, size_t > & indices() const
Get indices buffer (read-only)
Definition sparse_vector.hpp:187
bool is_sorted() const
Check if indices are sorted.
Definition sparse_vector.hpp:198
SparseVector(size_t size)
Construct empty SparseVector.
Definition sparse_vector.hpp:99
DataBuffer< BackendTag, T > & data()
Get data buffer (read-write)
Definition sparse_vector.hpp:192
Backend-agnostic memory buffer with tag-based dispatch.
auto create(const World< T > &world, const heffte::box3d< int > &box)
Construct a new World object from an existing one and a box.
Definition decomposition.hpp:62
bool on_host(const SparseVector< BackendTag, T > &vec)
Check if SparseVector is on host.
Definition sparse_vector.hpp:227
Represents the global simulation domain (the "world").
Definition world.hpp:91