cuSBF
Loading...
Searching...
No Matches
Fastx.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <concepts>
5#include <cstdint>
6#include <filesystem>
7#include <format>
8#include <fstream>
9#include <istream>
10#include <memory>
11#include <span>
12#include <stdexcept>
13#include <string>
14#include <string_view>
15#include <utility>
16#include <vector>
17
18#include <cusbf/error.hpp>
19#include <cusbf/gzstreambuf.hpp>
20
21namespace cusbf {
22
26 uint64_t sequenceOffset{};
28 uint64_t sequenceBytes{};
29};
30
34 std::string_view sequence{};
36 std::span<const RecordRange> records{};
37};
38
47 public:
48 explicit DenseRecordBatchBuilder(uint64_t reserve_bytes = 0) {
49 if (reserve_bytes != 0) {
50 sequence_.reserve(static_cast<size_t>(reserve_bytes));
51 }
52 }
53
55 [[nodiscard]] std::string_view sequence_view() const noexcept {
56 return external_sequence_.empty() ? std::string_view{sequence_} : external_sequence_;
57 }
58
60 [[nodiscard]] RecordBatchView view() const noexcept {
61 return RecordBatchView{
63 std::span<const RecordRange>{ranges_.data(), ranges_.size()},
64 };
65 }
66
68 void appendRecord(std::string_view record_sequence) {
69 assert(external_sequence_.empty() && "cannot appendRecord when external_sequence_ is set");
70 ranges_.push_back(
72 static_cast<uint64_t>(sequence_.size()),
73 static_cast<uint64_t>(record_sequence.size()),
74 }
75 );
76 sequence_.append(record_sequence);
77 }
78
80 void push_range(RecordRange range) {
81 ranges_.push_back(range);
82 }
83
84 [[nodiscard]] std::string& sequence_buffer() noexcept {
85 return sequence_;
86 }
87
88 [[nodiscard]] std::string_view& external_sequence_slot() noexcept {
89 return external_sequence_;
90 }
91
92 [[nodiscard]] bool empty() const noexcept {
93 return ranges_.empty();
94 }
95
96 [[nodiscard]] uint64_t recordCount() const noexcept {
97 return static_cast<uint64_t>(ranges_.size());
98 }
99
101 [[nodiscard]] uint64_t raw_sequence_bytes() const noexcept {
102 if (!external_sequence_.empty()) {
103 uint64_t total = 0;
104 for (const RecordRange& range : ranges_) {
105 total += range.sequenceBytes;
106 }
107 return total;
108 }
109 return static_cast<uint64_t>(sequence_.size());
110 }
111
112 [[nodiscard]] const std::vector<RecordRange>& ranges() const noexcept {
113 return ranges_;
114 }
115
116 void clear() {
117 sequence_.clear();
118 ranges_.clear();
119 external_sequence_ = {};
120 }
121
123 clear();
124 std::string{}.swap(sequence_);
125 std::vector<RecordRange>{}.swap(ranges_);
126 }
127
128 private:
129 std::string sequence_;
130 std::string_view external_sequence_{};
131 std::vector<RecordRange> ranges_;
132};
133
137 uint64_t record_index{};
139 std::string_view sequence{};
141 uint64_t queriedBases{};
143 uint64_t queriedKmers{};
145 uint64_t positive_kmers{};
147 std::span<const uint8_t> hits{};
148};
149
153 uint64_t recordsIndexed{};
155 uint64_t indexedBases{};
157 uint64_t insertedKmers{};
158};
159
163 uint64_t recordsQueried{};
165 uint64_t queriedBases{};
167 uint64_t queriedKmers{};
169 uint64_t positive_kmers{};
170};
171
175 uint64_t record_index{};
177 std::string_view header{};
179 std::string_view sequence{};
181 uint64_t queriedBases{};
183 uint64_t queriedKmers{};
185 uint64_t positive_kmers{};
187 std::span<const uint8_t> hits{};
188};
189
193 uint64_t record_index{};
195 std::string header;
197 std::string sequence;
199 uint64_t queriedBases{};
201 uint64_t queriedKmers{};
203 uint64_t positive_kmers{};
205 std::vector<uint8_t> hits;
206};
207
213 std::vector<FastxDetailedQueryRecord> records;
214};
215
223template <typename Functor>
224concept RecordQueryConsumer = requires(Functor f, const RecordQueryView& view) {
225 { f(view) } -> std::same_as<void>;
226};
227
235template <typename Functor>
236concept FastxRecordConsumer = requires(Functor f, const FastxRecordView& record) {
237 { f(record) } -> std::same_as<void>;
238};
239
240namespace detail {
241
243enum class FastxFormat : uint8_t {
245 unknown,
247 fasta,
249 fastq,
250};
251
255 std::string header;
257 std::string sequence;
258};
259
261inline void trimTrailingCarriageReturn(std::string& line) {
262 if (!line.empty() && line.back() == '\r') {
263 line.pop_back();
264 }
265}
266
268[[nodiscard]] inline uint32_t fastx_column_at(std::string_view line, size_t byte_index) {
269 if (line.empty()) {
270 return 1;
271 }
272 return static_cast<uint32_t>(std::min(byte_index, line.size() - 1) + 1);
273}
274
279 std::string_view line
280) {
281 const uint64_t before = quality_length - line.size();
282 const size_t byte_index = expected_length > before ? expected_length - before : line.size();
283 return fastx_column_at(line, byte_index);
284}
285
287[[nodiscard]] inline uint32_t fastx_quality_short_column(std::string_view line) {
288 return static_cast<uint32_t>(line.empty() ? 1 : line.size() + 1);
289}
290
298 public:
305 explicit FastxReader(std::istream& input, std::string_view source_name = "<stream>")
306 : input_(input), source_name_(source_name) {}
307
315 record.header.clear();
316 record.sequence.clear();
317
318 const auto header = readHeaderLine();
319 if (!header) {
320 return Err(header.error());
321 }
322 if (header->empty()) {
323 return false;
324 }
325
326 const char headerTag = header->front();
327
328 if (format_ == FastxFormat::unknown) {
329 if (headerTag == '>') {
330 format_ = FastxFormat::fasta;
331 } else if (headerTag == '@') {
332 format_ = FastxFormat::fastq;
333 } else {
334 return Err(
335 parseError("expected FASTA or FASTQ header", fastx_column_at(*header, 0))
336 );
337 }
338 }
339
340 const char expectedHeader = format_ == FastxFormat::fasta ? '>' : '@';
341 if (headerTag != expectedHeader) {
342 return Err(parseError(
343 "mixed FASTA and FASTQ records are not supported", fastx_column_at(*header, 0)
344 ));
345 }
346
347 record.header.assign(header->substr(1));
348 if (format_ == FastxFormat::fasta) {
349 CUSBF_TRY(readFastaSequence(record.sequence));
350 } else {
351 CUSBF_TRY(readFastqSequence(record.sequence));
352 }
353 return true;
354 }
355
356 private:
357 std::istream& input_;
358 std::string source_name_;
359 std::string pendingHeader_;
360 std::string lineBuffer_;
362 uint64_t lineNumber_{};
363
364 [[nodiscard]] Error parseError(std::string_view message, uint32_t column) const {
365 return Error::fastx_parse(
366 SourceLocation::fastx(source_name_, static_cast<uint32_t>(lineNumber_), column), message
367 );
368 }
369
370 [[nodiscard]] Result<std::string_view> readHeaderLine() {
371 if (!pendingHeader_.empty()) {
372 lineBuffer_ = std::move(pendingHeader_);
373 pendingHeader_.clear();
374 return std::string_view{lineBuffer_};
375 }
376
377 while (std::getline(input_, lineBuffer_)) {
378 ++lineNumber_;
379 trimTrailingCarriageReturn(lineBuffer_);
380 if (!lineBuffer_.empty()) {
381 return std::string_view{lineBuffer_};
382 }
383 }
384
385 if (input_.bad()) {
386 return Err(
387 Error::io(std::format("Failed to read FASTA/FASTQ input from {}", source_name_))
388 );
389 }
390 return std::string_view{};
391 }
392
393 [[nodiscard]] Result<void> readFastaSequence(std::string& sequence) {
394 sequence.reserve(4096);
395 while (std::getline(input_, lineBuffer_)) {
396 ++lineNumber_;
397 trimTrailingCarriageReturn(lineBuffer_);
398 if (!lineBuffer_.empty() && lineBuffer_.front() == '>') {
399 pendingHeader_ = std::move(lineBuffer_);
400 return {};
401 }
402 sequence += lineBuffer_;
403 }
404
405 if (input_.bad()) {
406 return Err(
407 Error::io(std::format("Failed to read FASTA/FASTQ input from {}", source_name_))
408 );
409 }
410 return {};
411 }
412
413 [[nodiscard]] Result<void> readFastqSequence(std::string& sequence) {
414 sequence.reserve(4096);
415 while (std::getline(input_, lineBuffer_)) {
416 ++lineNumber_;
417 trimTrailingCarriageReturn(lineBuffer_);
418 if (!lineBuffer_.empty() && lineBuffer_.front() == '+') {
419 CUSBF_TRY(readFastqQualities(sequence.size()));
420 return {};
421 }
422 sequence += lineBuffer_;
423 }
424
425 if (input_.bad()) {
426 return Err(
427 Error::io(std::format("Failed to read FASTA/FASTQ input from {}", source_name_))
428 );
429 }
430 return Err(parseError(
431 "unterminated FASTQ record: missing '+' separator",
432 fastx_column_at(lineBuffer_, lineBuffer_.size() > 0 ? lineBuffer_.size() - 1 : 0)
433 ));
434 }
435
436 [[nodiscard]] Result<void> readFastqQualities(uint64_t expectedLength) {
439 while (qualityLength < expectedLength && std::getline(input_, lineBuffer_)) {
440 ++lineNumber_;
441 trimTrailingCarriageReturn(lineBuffer_);
443 qualityLength += lineBuffer_.size();
445 return Err(parseError(
446 "FASTQ quality length exceeds sequence length",
448 ));
449 }
450 }
451
453 return {};
454 }
455 if (input_.bad()) {
456 return Err(
457 Error::io(std::format("Failed to read FASTA/FASTQ input from {}", source_name_))
458 );
459 }
460 return Err(parseError("FASTQ quality length does not match sequence length", short_column));
461 }
462};
463
471 const std::filesystem::path& path
472) {
473 if (isGzipFile(path)) {
474 return GzIstream::open(path);
475 }
476 auto input = std::make_unique<std::ifstream>(path);
477 if (!input->is_open()) {
478 return Err(Error::io(std::format("Failed to open FASTA/FASTQ file: {}", path.string())));
479 }
480 return input;
481}
482
483} // namespace detail
484
485} // namespace cusbf
Accumulates parsed FASTX records into a dense RecordBatchView.
Definition Fastx.hpp:46
bool empty() const noexcept
Definition Fastx.hpp:92
uint64_t raw_sequence_bytes() const noexcept
Sum of record payload bytes (ignores mmap storage outside ranges).
Definition Fastx.hpp:101
std::string_view & external_sequence_slot() noexcept
Definition Fastx.hpp:88
RecordBatchView view() const noexcept
Non-owning RecordBatchView over the accumulated records.
Definition Fastx.hpp:60
void push_range(RecordRange range)
Records a range already present in sequence_view.
Definition Fastx.hpp:80
DenseRecordBatchBuilder(uint64_t reserve_bytes=0)
Definition Fastx.hpp:48
uint64_t recordCount() const noexcept
Definition Fastx.hpp:96
void appendRecord(std::string_view record_sequence)
Appends one record payload and records its byte range.
Definition Fastx.hpp:68
const std::vector< RecordRange > & ranges() const noexcept
Definition Fastx.hpp:112
std::string_view sequence_view() const noexcept
Dense sequence buffer backing view (owned or mmap-external).
Definition Fastx.hpp:55
std::string & sequence_buffer() noexcept
Definition Fastx.hpp:84
Streaming FASTA/FASTQ parser.
Definition Fastx.hpp:297
FastxReader(std::istream &input, std::string_view source_name="<stream>")
Constructs a reader over an open input stream.
Definition Fastx.hpp:305
Result< bool > nextRecord(FastxRecord &record)
Reads the next FASTA/FASTQ record into record.
Definition Fastx.hpp:314
static Result< std::unique_ptr< GzIstream > > open(const std::filesystem::path &path)
Opens a gzip file as an input stream.
Callable invoked once per record by Filter::query_fastx_records and related FASTX streaming query API...
Definition Fastx.hpp:236
Callable invoked once per record by Filter::query_record_batch overloads that take a callback.
Definition Fastx.hpp:224
#define CUSBF_TRY(expr)
Propagates a cusbf::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:246
bool isGzipFile(const std::filesystem::path &path)
True when path begins with the gzip magic bytes (0x1F, 0x8B).
void trimTrailingCarriageReturn(std::string &line)
Removes a trailing carriage return from line if present (Windows line endings).
Definition Fastx.hpp:261
Result< std::unique_ptr< std::istream > > openFastxFile(const std::filesystem::path &path)
Opens a FASTA/FASTQ file for reading.
Definition Fastx.hpp:470
uint32_t fastx_column_at(std::string_view line, size_t byte_index)
1-based column at byte_index within line (clamped to the line end).
Definition Fastx.hpp:268
uint32_t fastx_quality_short_column(std::string_view line)
1-based column where a quality run ends too short (position after the last byte).
Definition Fastx.hpp:287
uint32_t fastx_quality_excess_column(uint64_t quality_length, uint64_t expected_length, std::string_view line)
1-based column of the first quality byte that exceeds expected_length.
Definition Fastx.hpp:276
FastxFormat
Detected file format for a FASTA/FASTQ stream.
Definition Fastx.hpp:243
@ fasta
FASTA (> headers).
@ fastq
FASTQ (@ headers).
@ unknown
Format not yet determined from the first header.
consteval bool separatorPositionAlwaysEncodesInvalid(char *input, uint64_t separatorPosition, uint64_t index)
Recursively tests whether placing the separator byte at any position in an input of valid bytes alway...
Definition Alphabet.cuh:37
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
Definition error.hpp:219
static Error io(std::string message)
Definition error.hpp:119
static Error fastx_parse(SourceLocation site, std::string_view detail)
Definition error.hpp:123
Detailed per-record query results returned by Filter FASTX detail APIs.
Definition Fastx.hpp:191
std::vector< uint8_t > hits
Per-k-mer hits retained after the API returns.
Definition Fastx.hpp:205
std::string sequence
Owning copy of the record sequence.
Definition Fastx.hpp:197
std::string header
Owning copy of the record header.
Definition Fastx.hpp:195
uint64_t queriedBases
Bases included in the query window.
Definition Fastx.hpp:199
uint64_t positive_kmers
K-mers reported present.
Definition Fastx.hpp:203
uint64_t queriedKmers
K-mer windows evaluated.
Definition Fastx.hpp:201
uint64_t record_index
Index in the input stream or file.
Definition Fastx.hpp:193
Aggregate and per-record results returned by Filter FASTX detail APIs.
Definition Fastx.hpp:209
FastxQueryReport summary
Totals across all records.
Definition Fastx.hpp:211
std::vector< FastxDetailedQueryRecord > records
One entry per record in source order.
Definition Fastx.hpp:213
Summary statistics returned by Filter insert operations on FASTX and record-batch input.
Definition Fastx.hpp:151
uint64_t indexedBases
Total sequence bytes indexed.
Definition Fastx.hpp:155
uint64_t recordsIndexed
Records parsed and indexed.
Definition Fastx.hpp:153
uint64_t insertedKmers
K-mer windows inserted (valid symbols only).
Definition Fastx.hpp:157
Summary statistics returned by Filter query operations on FASTX and record-batch input.
Definition Fastx.hpp:161
uint64_t queriedBases
Total sequence bytes queried.
Definition Fastx.hpp:165
uint64_t recordsQueried
Records parsed and queried.
Definition Fastx.hpp:163
uint64_t queriedKmers
K-mer windows evaluated.
Definition Fastx.hpp:167
uint64_t positive_kmers
K-mers reported present in the filter.
Definition Fastx.hpp:169
Per-record query payload emitted by FASTX streaming query APIs.
Definition Fastx.hpp:173
uint64_t queriedKmers
K-mer windows evaluated.
Definition Fastx.hpp:183
std::string_view header
FASTA/FASTQ header (without leading > or @).
Definition Fastx.hpp:177
uint64_t positive_kmers
K-mers reported present.
Definition Fastx.hpp:185
std::string_view sequence
Record sequence bytes.
Definition Fastx.hpp:179
uint64_t queriedBases
Bases included in the query window.
Definition Fastx.hpp:181
uint64_t record_index
Index in the input stream or file.
Definition Fastx.hpp:175
std::span< const uint8_t > hits
Per-k-mer hits (1 = present), valid only for the callback duration.
Definition Fastx.hpp:187
Dense host-resident sequence batch plus explicit record boundaries.
Definition Fastx.hpp:32
std::span< const RecordRange > records
Ordered, non-overlapping record ranges.
Definition Fastx.hpp:36
std::string_view sequence
Raw record payloads concatenated.
Definition Fastx.hpp:34
Per-record query payload emitted by query_record_batch().
Definition Fastx.hpp:135
uint64_t queriedKmers
K-mer windows evaluated (valid symbols only).
Definition Fastx.hpp:143
std::span< const uint8_t > hits
Per-k-mer hits (1 = present), valid only in callback.
Definition Fastx.hpp:147
uint64_t positive_kmers
K-mers reported present in the filter.
Definition Fastx.hpp:145
std::string_view sequence
Record sequence slice (normalized layout).
Definition Fastx.hpp:139
uint64_t queriedBases
Bases included in the query window.
Definition Fastx.hpp:141
uint64_t record_index
Index in the source batch.
Definition Fastx.hpp:137
Ordered non-overlapping byte range for one record inside a dense sequence batch.
Definition Fastx.hpp:24
uint64_t sequenceOffset
Byte offset into RecordBatchView::sequence.
Definition Fastx.hpp:26
uint64_t sequenceBytes
Payload length in bytes (symbol-aligned).
Definition Fastx.hpp:28
Fallible API result: cuda::std::expected<T, Error> with cuSBF factories.
Definition error.hpp:152
static SourceLocation fastx(std::string_view file, uint32_t line, uint32_t column)
Definition error.hpp:34
A single sequence record extracted from a FASTA/FASTQ stream.
Definition Fastx.hpp:253
std::string header
Header line without the leading > or .
Definition Fastx.hpp:255
std::string sequence
Concatenated sequence lines (qualities skipped for FASTQ).
Definition Fastx.hpp:257