cuSBF
Loading...
Searching...
No Matches
normalized_record_batch.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <span>
4
5#include <cstdint>
6#include <cstring>
8#include <stdexcept>
9#include <string>
10#include <string_view>
11#include <vector>
12
13#include <cusbf/config.cuh>
14#include <cusbf/error.hpp>
15#include <cusbf/Fastx.hpp>
16
17namespace cusbf {
18
22 uint64_t record_index{};
24 uint64_t input_offset{};
26 uint64_t size{};
28 uint64_t valid_kmers{};
29};
30
38 public:
40 [[nodiscard]] const std::string& sequence() const noexcept {
41 return sequence_;
42 }
43
45 [[nodiscard]] std::span<const NormalizedRecord> records() const noexcept {
46 return std::span<const NormalizedRecord>{records_.data(), records_.size()};
47 }
48
50 [[nodiscard]] uint64_t total_valid_kmers() const noexcept {
51 uint64_t total = 0;
52 for (const NormalizedRecord& record : records_) {
53 total += record.valid_kmers;
54 }
55 return total;
56 }
57
58 private:
59 template <typename Config>
61
62 template <typename Config>
63 [[nodiscard]] static Result<NormalizedRecordBatch> from_batch(RecordBatchView batch) {
64 NormalizedRecordBatch prepared;
66 normalize_record_batch_into<Config>(batch, prepared.sequence_, prepared.records_)
67 );
68 return prepared;
69 }
70
71 std::string sequence_;
72 std::vector<NormalizedRecord> records_;
73};
74
75namespace detail {
76
77template <typename Config>
78[[nodiscard]] static bool sequence_may_have_invalid_symbols(std::string_view sequence) {
79 for (uint64_t offset = 0; offset < sequence.size(); offset += Config::symbolWidth) {
80 if (Config::Alphabet::encode(sequence.data() + offset) == Config::Alphabet::invalidSymbol) {
81 return true;
82 }
83 }
84 return false;
85}
86
87template <typename Config>
88[[nodiscard]] static uint64_t valid_record_kmer_count(std::string_view sequence) {
89 if (record_symbol_count<Config>(sequence.size()) < Config::k) {
90 return 0;
91 }
92
94 for (uint64_t i = 0; i < Config::k; ++i) {
95 invalid_symbols += Config::Alphabet::encode(sequence.data() + i * Config::symbolWidth) ==
96 Config::Alphabet::invalidSymbol;
97 }
98
99 uint64_t valid_kmers = invalid_symbols == 0 ? 1 : 0;
100 for (uint64_t start = 1; start < record_kmer_count<Config>(sequence.size()); ++start) {
102 Config::Alphabet::encode(sequence.data() + (start - 1) * Config::symbolWidth) ==
103 Config::Alphabet::invalidSymbol;
104 invalid_symbols += Config::Alphabet::encode(
105 sequence.data() + (start + Config::k - 1) * Config::symbolWidth
106 ) == Config::Alphabet::invalidSymbol;
107 valid_kmers += invalid_symbols == 0;
108 }
109 return valid_kmers;
110}
111
112template <typename Config>
113static void appendRecordBoundary(std::string& sequence) {
114 const uint64_t remainder = sequence.size() % Config::symbolWidth;
115 if (remainder != 0) {
116 sequence.append(
117 Config::symbolWidth - remainder, static_cast<char>(Config::Alphabet::separator)
118 );
119 }
120 sequence.append(Config::symbolWidth, static_cast<char>(Config::Alphabet::separator));
121}
122
123template <typename Config>
124[[nodiscard]] static Result<void> validateRecordBatch(RecordBatchView batch) {
126 for (const RecordRange& record : batch.records) {
127 if (record.sequenceOffset < next_offset) {
128 return Err(
129 Error::invalid_argument("record batch ranges must be ordered and non-overlapping")
130 );
131 }
132 if (record.sequenceOffset > batch.sequence.size() ||
133 record.sequenceBytes > batch.sequence.size() - record.sequenceOffset) {
134 return Err(Error::invalid_argument("record batch range exceeds the source sequence"));
135 }
136 if (record.sequenceOffset % Config::symbolWidth != 0 ||
137 record.sequenceBytes % Config::symbolWidth != 0) {
138 return Err(
140 "record batch ranges must align to the configured alphabet symbol width"
141 )
142 );
143 }
144 next_offset = record.sequenceOffset + record.sequenceBytes;
145 }
146 return {};
147}
148
149template <typename Config>
150static void appendPreparedRecord(
151 std::string& output,
152 std::vector<NormalizedRecord>& ranges,
153 uint64_t record_index,
154 uint64_t input_offset,
155 std::string_view record_sequence
156) {
157 if (!output.empty()) {
159 }
160 output.append(record_sequence);
163 : record_kmer_count<Config>(record_sequence.size());
164 ranges.push_back(
165 NormalizedRecord{
166 record_index,
167 input_offset,
168 static_cast<uint64_t>(record_sequence.size()),
169 valid_kmers,
170 }
171 );
172}
173
174template <typename Config>
175[[nodiscard]] static uint64_t normalized_batch_logical_size(uint64_t current_size) noexcept {
177}
178
179template <typename Config>
180[[nodiscard]] inline uint64_t estimate_normalized_batch_bytes(RecordBatchView batch) noexcept {
181 uint64_t size = 0;
182 for (uint64_t record_index = 0; record_index < batch.records.size(); ++record_index) {
183 const RecordRange& record = batch.records[record_index];
184 if (record_index != 0 || size != 0) {
186 if (remainder != 0) {
188 }
189 size += Config::symbolWidth;
190 }
191 size += record.sequenceBytes;
192 }
193 return size;
194}
195
196template <typename Config>
197[[nodiscard]] static bool records_have_only_valid_symbols(RecordBatchView batch) {
198 for (const RecordRange& record : batch.records) {
199 const std::string_view record_sequence =
200 batch.sequence.substr(record.sequenceOffset, record.sequenceBytes);
202 return false;
203 }
204 }
205 return true;
206}
207
208template <typename Config>
209static char* write_record_boundary(char* write_cursor, uint64_t& logical_size) {
210 if (logical_size == 0) {
211 return write_cursor;
212 }
213
215 if (remainder != 0) {
217 std::memset(
218 write_cursor, static_cast<int>(Config::Alphabet::separator), static_cast<size_t>(pad)
219 );
220 write_cursor += pad;
221 logical_size += pad;
222 }
223
224 std::memset(
226 static_cast<int>(Config::Alphabet::separator),
227 static_cast<size_t>(Config::symbolWidth)
228 );
231 return write_cursor;
232}
233
234template <typename Config>
235inline void normalize_record_batch_into_buffer(
236 RecordBatchView batch,
237 char* sequence_out,
238 size_t& sequence_out_bytes,
239 std::vector<NormalizedRecord>& records_out
240) {
241 records_out.clear();
242 if (batch.records.size() <= 4096) {
243 records_out.reserve(batch.records.size());
244 }
245
247
250 for (uint64_t record_index = 0; record_index < batch.records.size(); ++record_index) {
251 const RecordRange& record = batch.records[record_index];
252 const std::string_view record_sequence =
253 batch.sequence.substr(record.sequenceOffset, record.sequenceBytes);
254
256 std::memcpy(
257 write_cursor, record_sequence.data(), static_cast<size_t>(record_sequence.size())
258 );
261
262 const uint64_t valid_kmers = records_are_clean
264 : valid_record_kmer_count<Config>(record_sequence);
265 records_out.push_back(
266 NormalizedRecord{
267 record_index,
268 record.sequenceOffset,
269 static_cast<uint64_t>(record_sequence.size()),
270 valid_kmers,
271 }
272 );
273 }
274
275 sequence_out_bytes = static_cast<size_t>(write_cursor - sequence_out);
276}
277
278} // namespace detail
279
288template <typename Config>
290 RecordBatchView batch,
291 std::string& sequence_out,
292 std::vector<NormalizedRecord>& records_out
293) {
295
296 const uint64_t estimated_bytes = detail::estimate_normalized_batch_bytes<Config>(batch);
297 sequence_out.resize(static_cast<size_t>(estimated_bytes));
298 size_t sequence_out_bytes = 0;
300 batch, sequence_out.data(), sequence_out_bytes, records_out
301 );
302 sequence_out.resize(sequence_out_bytes);
303 return {};
304}
305
313template <typename Config>
315 return NormalizedRecordBatch::from_batch<Config>(batch);
316}
317
318} // namespace cusbf
Host-resident Normalized record batch ready for bulk sequence insert/query.
uint64_t total_valid_kmers() const noexcept
Sum of NormalizedRecord::valid_kmers across all records.
const std::string & sequence() const noexcept
Dense normalized sequence (includes inter-record separators).
std::span< const NormalizedRecord > records() const noexcept
Per-record metadata in source order.
friend Result< NormalizedRecordBatch > normalize_record_batch(RecordBatchView batch)
Builds a normalized record batch from a dense RecordBatchView.
#define CUSBF_TRY(expr)
Propagates a cusbf::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:246
constexpr __host__ __device__ uint64_t record_kmer_count(uint64_t bases) noexcept
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
Result< void > normalize_record_batch_into(RecordBatchView batch, std::string &sequence_out, std::vector< NormalizedRecord > &records_out)
Builds a normalized record batch into reusable host buffers.
Result< NormalizedRecordBatch > normalize_record_batch(RecordBatchView batch)
Builds a normalized record batch from a dense RecordBatchView.
static constexpr uint64_t symbolWidth
Input bytes per symbol.
Definition config.cuh:49
static constexpr uint16_t k
K-mer length in symbols.
Definition config.cuh:39
static Error invalid_argument(std::string message)
Definition error.hpp:131
Per-record metadata inside a normalized record batch.
uint64_t valid_kmers
K-mer windows with no invalid symbols in this record.
uint64_t record_index
Index in the source RecordBatchView.
uint64_t input_offset
Byte offset in the dense input sequence.
uint64_t size
Record payload size in bytes after normalization.
Dense host-resident sequence batch plus explicit record boundaries.
Definition Fastx.hpp:32
Fallible API result: cuda::std::expected<T, Error> with cuSBF factories.
Definition error.hpp:152