cuDDL
Loading...
Searching...
No Matches
fasta_parser.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cuda/std/cstdint>
4
5#include <memory>
6#include <optional>
7#include <string>
8#include <string_view>
9#include <thread>
10#include <utility>
11#include <vector>
12
13#include <cuddl/detail/dna.hpp>
15
16#include <cuddl/error.hpp>
17
18namespace cuddl::detail {
19
23 std::vector<uint64_t> kmers;
25 uint64_t bases = 0;
27 uint64_t valid_kmers = 0;
29 uint64_t invalid_windows = 0;
30};
31
39#ifdef __CUDACC__
40__host__ __device__
41#endif
42inline constexpr uint64_t reverse_complement(uint64_t packed, uint32_t k) noexcept {
43 if (k > 32) {
44 uint64_t result = 0;
45 for (uint32_t base = 0; base < k; ++base) {
46 result = (result << 2U) | static_cast<uint64_t>(static_cast<uint8_t>(packed & 3U) ^ 2U);
47 packed >>= 2U;
48 }
49 return result;
50 }
51 uint64_t x = packed;
52 x = ((x & 0xCCCCCCCCCCCCCCCCULL) >> 2U) | ((x & 0x3333333333333333ULL) << 2U);
53 x = ((x & 0xF0F0F0F0F0F0F0F0ULL) >> 4U) | ((x & 0x0F0F0F0F0F0F0F0FULL) << 4U);
54 x = ((x & 0xFF00FF00FF00FF00ULL) >> 8U) | ((x & 0x00FF00FF00FF00FFULL) << 8U);
55 x = ((x & 0xFFFF0000FFFF0000ULL) >> 16U) | ((x & 0x0000FFFF0000FFFFULL) << 16U);
56 x = (x >> 32U) | (x << 32U);
57 x >>= (64U - 2U * k);
58 auto const mask = (k == 32) ? ~uint64_t{0} : ((1ULL << (2U * k)) - 1ULL);
59 return (x ^ 0xAAAAAAAAAAAAAAAAULL) & mask;
60}
61
69 std::string prefix;
70 std::vector<std::string_view> segments;
71 uint64_t total_bytes = 0;
72};
73
74namespace {
75
76inline bool span_step_back(
77 std::vector<fastx_sequence_extent> const& extents,
78 size_t& extent,
79 char const*& position
80) {
81 if (position > extents[extent].begin) {
82 --position;
83 return true;
84 }
85 if (extent > 0) {
86 --extent;
87 position = extents[extent].end - 1;
88 return true;
89 }
90 return false;
91}
92
93inline bool span_step_forward(
94 std::vector<fastx_sequence_extent> const& extents,
95 size_t& extent,
96 char const*& position
97) {
98 if (position + 1 < extents[extent].end) {
99 ++position;
100 return true;
101 }
102 if (extent + 1 < extents.size()) {
103 ++extent;
104 position = extents[extent].begin;
105 return true;
106 }
107 return false;
108}
109
110} // namespace
111
122[[nodiscard]] inline std::vector<fastx_sequence_span> fastx_split_sequence_spans(
123 std::vector<fastx_sequence_extent> const& extents,
124 uint32_t seed_bases,
125 uint32_t count
126) {
127 std::vector<fastx_sequence_span> spans;
128 if (extents.empty() || count == 0U) {
129 return spans;
130 }
131 size_t total = 0;
132 for (auto const& extent : extents) {
133 total += static_cast<size_t>(extent.end - extent.begin);
134 }
135 if (total == 0) {
136 return spans;
137 }
138 if (static_cast<size_t>(count) > total) {
139 count = static_cast<uint32_t>(total);
140 }
141 auto const per = total / count + (total % count != 0U);
142 spans.reserve(count);
143
144 for (uint32_t t = 0; t < count; ++t) {
145 auto const span_begin = static_cast<size_t>(t) * per;
146 if (span_begin >= total) {
147 break;
148 }
149 auto const span_end = span_begin + per < total ? span_begin + per : total;
150
152 span.total_bytes = span_end - span_begin;
153
154 // Locate the extent and offset of the span start.
155 size_t extent = 0;
156 size_t offset = span_begin;
157 while (extent < extents.size() &&
158 offset >= static_cast<size_t>(extents[extent].end - extents[extent].begin)) {
159 offset -= static_cast<size_t>(extents[extent].end - extents[extent].begin);
160 ++extent;
161 }
162
163 // Seed: walk back up to seed_bases valid bases (or until an invalid base / EOF).
164 size_t seed_extent = extent;
165 char const* seed_start = extents[extent].begin + offset;
166 {
167 size_t e = extent;
168 char const* q = seed_start;
169 uint32_t collected = 0;
170 while (collected < seed_bases && span_step_back(extents, e, q)) {
171 auto const ch = *q;
173 continue;
174 }
175 if (encode_base(ch) == 0xffu) {
176 seed_extent = e;
177 seed_start = q;
178 span_step_forward(extents, seed_extent, seed_start);
179 break;
180 }
181 ++collected;
182 seed_extent = e;
183 seed_start = q;
184 }
185 }
186
187 // Materialise the prefix from the seed start to the span start.
188 {
189 size_t e = seed_extent;
190 char const* q = seed_start;
191 char const* target = extents[extent].begin + offset;
192 while (e < extent || q < target) {
193 if (q >= extents[e].end) {
194 ++e;
195 q = extents[e].begin;
196 continue;
197 }
198 span.prefix.push_back(*q);
199 ++q;
200 }
201 }
202
203 // Collect the span's segments.
204 {
205 size_t remaining = span_end - span_begin;
206 char const* q = extents[extent].begin + offset;
207 size_t e = extent;
208 while (remaining > 0 && e < extents.size()) {
209 auto const limit = static_cast<size_t>(extents[e].end - q);
210 auto const step = limit < remaining ? limit : remaining;
211 span.segments.emplace_back(q, step);
212 remaining -= step;
213 if (remaining > 0) {
214 ++e;
215 if (e < extents.size()) {
216 q = extents[e].begin;
217 }
218 }
219 }
220 }
221
222 spans.push_back(std::move(span));
223 }
224 return spans;
225}
226
229 std::vector<uint64_t> kmers;
230 uint64_t bases = 0;
231 uint64_t valid_kmers = 0;
232 uint64_t invalid_windows = 0;
233};
234
236inline void consume_byte(
237 char ch,
238 uint32_t k,
239 uint64_t mask,
240 span_result& result,
241 uint64_t& window,
242 uint32_t& window_len
243) {
245 return;
246 }
247 auto const encoded = encode_base(ch);
248 if (encoded == 0xffu) {
249 if (window_len < k && window_len > 0) {
250 ++result.invalid_windows;
251 }
252 window_len = 0;
253 window = 0;
254 return;
255 }
256 ++result.bases;
257 window = ((window << 2U) | encoded) & mask;
258 if (window_len < k) {
259 ++window_len;
260 }
261 if (window_len == k) {
262 auto const rev = reverse_complement(window, k);
263 result.kmers.push_back(window > rev ? window : rev);
264 ++result.valid_kmers;
265 }
266}
267
289 std::string const& path,
290 uint32_t k,
291 unsigned threads = std::thread::hardware_concurrency()
292) {
293 auto source = CUDDL_TRY(load_fastx_sequence_file(path));
294 auto& extents = source->extents;
295 if (extents.empty()) return fasta_parse_result{};
296
297 // The span splitter otherwise joins extents and seeds windows across record headers.
298 // A separate invalid-base extent stops prefix replay. Workers recognize its address and
299 // reset without counting the synthetic separator as an invalid input window.
300 static constexpr char record_boundary = 'N';
301 if (extents.size() > 1) {
302 std::vector<fastx_sequence_extent> separated;
303 separated.reserve(extents.size() * 2 - 1);
304 for (auto const& extent : extents) {
305 if (!separated.empty()) separated.push_back({&record_boundary, &record_boundary + 1});
306 separated.push_back(extent);
307 }
308 extents = std::move(separated);
309 }
310
311 uint64_t total = 0;
312 for (auto const& extent : extents) {
313 total += static_cast<uint64_t>(extent.end - extent.begin);
314 }
315
316 unsigned worker_count = 1;
317 if (total >= (uint64_t{1} << 20)) {
318 auto const available = threads > 0 ? threads : 1U;
319 worker_count = available > 0 ? available : 1U;
320 }
321
322 auto const spans = fastx_split_sequence_spans(extents, k > 0 ? k - 1U : 0U, worker_count);
323
324 auto const mask = (k >= 32) ? ~uint64_t{0} : ((1ULL << (2U * k)) - 1ULL);
325 std::vector<span_result> partials(spans.size());
326 auto const per_thread = total / spans.size() + (total % spans.size() != 0U);
327 for (auto& partial : partials) {
328 partial.kmers.reserve(per_thread / 2U + 64U);
329 }
330
331 std::vector<std::thread> workers;
332 workers.reserve(spans.size());
333 for (size_t t = 0; t < spans.size(); ++t) {
334 workers.emplace_back([&, t] {
335 uint64_t window = 0;
336 uint32_t window_len = 0;
337 span_result sink{}; // prefix replay: no counts, no emissions
338 for (char ch : spans[t].prefix) {
339 consume_byte(ch, k, mask, sink, window, window_len);
340 }
341 for (auto const& segment : spans[t].segments) {
342 if (segment.data() == &record_boundary) {
343 window = 0;
344 window_len = 0;
345 continue;
346 }
347 for (char ch : segment) {
348 consume_byte(ch, k, mask, partials[t], window, window_len);
349 }
350 }
351 });
352 }
353 for (auto& worker : workers) {
354 worker.join();
355 }
356
357 fasta_parse_result result;
358 uint64_t total_valid = 0;
359 for (auto const& partial : partials) {
360 result.bases += partial.bases;
361 total_valid += partial.valid_kmers;
362 result.invalid_windows += partial.invalid_windows;
363 }
364 result.valid_kmers = total_valid;
365 // One contiguous allocation for the final stream: reserve the exact count, then move each
366 // partial in and release it immediately so the peak never exceeds the final size plus one
367 // partial (the machine's working set is the result stream itself).
368 result.kmers.reserve(total_valid);
369 for (auto& partial : partials) {
370 result.kmers.insert(
371 result.kmers.end(),
372 std::make_move_iterator(partial.kmers.begin()),
373 std::make_move_iterator(partial.kmers.end())
374 );
375 partial.kmers = std::vector<uint64_t>{};
376 }
377 return result;
378}
379
380} // namespace cuddl::detail
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:225
auto const count
Definition kernels.cuh:1031
Result< fasta_parse_result > parse_fasta(std::string const &path, uint32_t k, unsigned threads=std::thread::hardware_concurrency())
Parses a FASTA/FASTQ file's sequence bases into packed canonical k-mers of length k.
constexpr uint64_t reverse_complement(uint64_t packed, uint32_t k) noexcept
Reverse-complements a packed k-mer (2 bits per base) with bit-parallel reversal.
auto const total
Definition kernels.cuh:443
constexpr bool fastx_is_sequence_whitespace(char ch) noexcept
True for bytes that FASTA sequence consumers conventionally skip between bases.
__global__ uint32_t const uint32_t const uint32_t const uint32_t uint32_t size_t size_t uint32_t SearchResult * packed
Definition kernels.cuh:1791
Result< std::unique_ptr< fastx_sequence_file > > load_fastx_sequence_file(std::string const &path, decompression_source source={}, bool defer_fasta=false, bool read=false, uint32_t pack_k=0)
void consume_byte(char ch, uint32_t k, uint64_t mask, span_result &result, uint64_t &window, uint32_t &window_len)
Feeds one byte of sequence through the DDL rolling-window extraction.
constexpr uint8_t encode_base(char base) noexcept
Maps one DNA byte to its 2-bit symbol (A=0, C=1, T=2, G=3) or 0xff when invalid.
Definition dna.hpp:16
std::vector< fastx_sequence_span > fastx_split_sequence_spans(std::vector< fastx_sequence_extent > const &extents, uint32_t seed_bases, uint32_t count)
Splits the combined FASTA sequence stream into count contiguous spans.
detail::fasta_parse_result fasta_parse_result
Result of parsing one FASTA/FASTQ file into packed canonical k-mers.
Definition fastx.hpp:16
Fallible API result: cuda::std::expected<T, Error> with cuDDL factories.
Definition error.hpp:118
Result of parsing one FASTA/FASTQ file into packed canonical k-mers.
std::vector< uint64_t > kmers
Packed canonical k-mers from all records, treated as one combined genome.
uint64_t bases
Total valid sequence bases read.
uint64_t invalid_windows
Number of partial windows broken by an invalid base before reaching length k.
uint64_t valid_kmers
Number of k-mers emitted (one per rolling window of k valid bases).
One parallel scan span of the combined FASTA sequence stream.
std::vector< std::string_view > segments
One worker's rolling-window state and output.
std::vector< uint64_t > kmers