89 for (
char const character : token) {
90 auto const code =
static_cast<uint8_t
>(character);
91 if (code < 48U || code > 111U) {
94 value = (value << 6U) | static_cast<uint64_t>(code - 48U);
102 return std::string(1,
'0');
104 std::string reversed;
105 while (value != 0U) {
106 reversed.push_back(
static_cast<char>(48U + (value & 0x3fU)));
109 return {reversed.rbegin(), reversed.rend()};
118[[nodiscard]]
inline uint16_t
120 if (loaded == 0U || offset < 0) {
123 auto const mantissa_bits = 16U - exponent_bits;
124 auto const mask = (1U << mantissa_bits) - 1U;
125 auto const rel_nlz =
static_cast<uint32_t
>(loaded >> mantissa_bits);
126 auto const abs_nlz = rel_nlz +
static_cast<uint32_t
>(offset);
127 return static_cast<uint16_t
>((abs_nlz << mantissa_bits) | (loaded & mask));
135[[nodiscard]]
inline std::vector<std::string_view>
136split_fields(std::string_view text,
char separator,
bool keep_trailing_empty =
false) {
137 std::vector<std::string_view> fields;
140 auto const end = text.find(separator, begin);
141 if (end == std::string_view::npos) {
142 fields.push_back(text.substr(begin));
145 fields.push_back(text.substr(begin, end - begin));
148 if (!keep_trailing_empty && !fields.empty() && fields.back().empty()) {
157template <
typename T,
typename Parser>
160 return Result<T>::ok(
static_cast<T
>(parser(std::string(value))));
161 }
catch (std::exception
const&) {
171 return key ==
"id" || key ==
"tid" || key ==
"name" || key ==
"file" || key ==
"bases" ||
172 key ==
"contigs" || key ==
"gc" || key ==
"origin" || key ==
"lineage" || key ==
"len" ||
180 if (line.empty() || line.front() !=
'#') {
184 if (fields.empty()) {
187 auto key = fields.front();
188 key.remove_prefix(1);
189 if (fields.size() < 2U) {
192 auto const value = fields[1];
194 auto const parsed = detail::parse_field<int64_t>(
195 [](std::string
const& s) {
return std::stoll(s); }, value,
"id"
198 return Err(parsed.error());
201 }
else if (key ==
"tid") {
202 auto const parsed = detail::parse_field<int64_t>(
203 [](std::string
const& s) {
return std::stoll(s); }, value,
"tid"
206 return Err(parsed.error());
209 }
else if (key ==
"name") {
210 meta.
name = std::string(value);
211 }
else if (key ==
"file") {
213 }
else if (key ==
"bases") {
214 auto const parsed = detail::parse_field<int64_t>(
215 [](std::string
const& s) {
return std::stoll(s); }, value,
"bases"
218 return Err(parsed.error());
220 meta.
bases = *parsed;
221 }
else if (key ==
"contigs") {
222 auto const parsed = detail::parse_field<int32_t>(
223 [](std::string
const& s) {
return std::stol(s); }, value,
"contigs"
226 return Err(parsed.error());
229 }
else if (key ==
"gc") {
230 auto const parsed = detail::parse_field<float>(
231 [](std::string
const& s) {
return std::stof(s); }, value,
"gc"
234 return Err(parsed.error());
237 }
else if (key ==
"origin") {
238 meta.
origin = std::string(value);
239 }
else if (key ==
"lineage") {
240 meta.
lineage = std::string(value);
241 }
else if (key ==
"len") {
242 auto const parsed = detail::parse_field<uint32_t>(
243 [](std::string
const& s) {
return std::stoul(s); }, value,
"len"
246 return Err(parsed.error());
249 }
else if (key ==
"offset") {
250 auto const parsed = detail::parse_field<int32_t>(
251 [](std::string
const& s) {
return std::stol(s); }, value,
"offset"
254 return Err(parsed.error());
266 std::vector<uint16_t> scores;
267 scores.reserve(fields.size());
268 for (
auto const field : fields) {
273 "A48 data row contains a character outside the A48 alphabet"
294 uint8_t exponent_bits = 6U;
295 bool has_exponent =
false;
296 size_t expected_bucket_count = 0;
297 bool bucket_count_set =
false;
299 std::string header_block;
302 while (begin <= input.size()) {
303 auto const end = input.find(
'\n', begin);
305 input.substr(begin, end == std::string_view::npos ? input.size() - begin : end - begin);
306 if (!line.empty() && line.back() ==
'\r') {
307 line.remove_suffix(1);
311 if (end == std::string_view::npos) {
318 if (line.front() ==
'#') {
320 if (fields.empty()) {
323 auto key = fields.front();
324 key.remove_prefix(1);
325 auto const value = fields.size() > 1U ? fields[1] : std::string_view{};
328 auto const parsed = detail::parse_field<uint32_t>(
329 [](std::string
const& s) {
return std::stoul(s); }, value,
"k"
332 return Err(parsed.error());
336 }
else if (key ==
"seed") {
337 auto const parsed = detail::parse_field<uint64_t>(
338 [](std::string
const& s) {
return std::stoull(s); }, value,
"seed"
341 return Err(parsed.error());
345 }
else if (key ==
"exponent") {
346 auto const parsed = detail::parse_field<uint32_t>(
347 [](std::string
const& s) {
return std::stoul(s); }, value,
"exponent"
350 return Err(parsed.error());
352 if (*parsed < 1U || *parsed > 15U) {
355 exponent_bits = *parsed;
359 }
else if (key ==
"blacklist") {
364 if (!header_block.empty()) {
365 header_block.push_back(
'\n');
367 header_block.append(line);
370 if (end == std::string_view::npos) {
378 if (header_block.empty()) {
382 header_block.clear();
384 if (meta.offset >= 0 && !has_exponent) {
390 if (scores.empty()) {
393 if (!bucket_count_set) {
394 expected_bucket_count = scores.size();
395 bucket_count_set =
true;
396 }
else if (scores.size() != expected_bucket_count) {
399 if (meta.len != 0U && meta.len != scores.size()) {
404 decoded.
ordinal =
static_cast<uint32_t
>(ordinal);
406 decoded.
scores = std::move(scores);
407 result.
records.push_back(std::move(decoded));
410 if (end == std::string_view::npos) {
416 if (!header_block.empty()) {
436 std::string_view input,
437 unsigned requested_threads = std::thread::hardware_concurrency()
442 std::string_view data;
447 std::vector<record_job> jobs;
448 uint8_t exponent_bits = 6U;
449 bool has_exponent =
false;
450 size_t expected_bucket_count = 0;
451 bool bucket_count_set =
false;
453 std::string header_block;
456 while (begin <= input.size()) {
457 auto const end = input.find(
'\n', begin);
459 input.substr(begin, end == std::string_view::npos ? input.size() - begin : end - begin);
460 if (!line.empty() && line.back() ==
'\r') {
461 line.remove_suffix(1);
464 if (end == std::string_view::npos) {
471 if (line.front() ==
'#') {
473 if (fields.empty()) {
476 auto key = fields.front();
477 key.remove_prefix(1);
478 auto const value = fields.size() > 1U ? fields[1] : std::string_view{};
480 auto const parsed = detail::parse_field<uint32_t>(
481 [](std::string
const& s) {
return std::stoul(s); }, value,
"k"
484 return Err(parsed.error());
488 }
else if (key ==
"seed") {
489 auto const parsed = detail::parse_field<uint64_t>(
490 [](std::string
const& s) {
return std::stoull(s); }, value,
"seed"
493 return Err(parsed.error());
497 }
else if (key ==
"exponent") {
498 auto const parsed = detail::parse_field<uint32_t>(
499 [](std::string
const& s) {
return std::stoul(s); }, value,
"exponent"
502 return Err(parsed.error());
504 if (*parsed < 1U || *parsed > 15U) {
507 exponent_bits = *parsed;
511 }
else if (key ==
"blacklist") {
516 if (!header_block.empty()) {
517 header_block.push_back(
'\n');
519 header_block.append(line);
521 if (end == std::string_view::npos) {
528 if (header_block.empty()) {
531 auto const record_ordinal =
static_cast<uint32_t
>(ordinal);
532 if (record_ordinal == 0U) {
534 header_block.clear();
535 if (meta.offset >= 0 && !has_exponent) {
538 "relative-encoded A48 record requires a #exponent header"
543 if (scores.empty()) {
546 expected_bucket_count = scores.size();
547 bucket_count_set =
true;
548 if (meta.len != 0U && meta.len != scores.size()) {
552 decoded.
ordinal = record_ordinal;
554 decoded.
scores = std::move(scores);
555 result.
records.push_back(std::move(decoded));
558 record_job{std::move(header_block), line,
static_cast<uint32_t
>(ordinal)}
560 header_block.clear();
564 if (end == std::string_view::npos) {
570 if (!header_block.empty()) {
578 auto const requested = requested_threads;
579 auto const worker_count =
static_cast<size_t>(std::max(1U, requested));
580 std::vector<std::thread> workers;
581 std::vector<std::exception_ptr> errors(worker_count);
582 workers.reserve(worker_count);
584 for (
size_t worker = 0; worker < worker_count; ++worker) {
585 auto const chunk_begin = jobs.size() * worker / worker_count;
586 auto const chunk_end = jobs.size() * (worker + 1U) / worker_count;
587 if (chunk_begin == chunk_end) {
590 workers.emplace_back([&, chunk_begin, chunk_end, worker]() {
592 for (
size_t i = chunk_begin; i < chunk_end; ++i) {
593 auto const& job = jobs[i];
596 throw std::runtime_error(meta.error().message());
598 if (meta->offset >= 0 && !has_exponent) {
599 throw std::runtime_error(
600 "relative-encoded A48 record requires a #exponent header"
605 throw std::runtime_error(scores.error().message());
607 if (scores->empty()) {
608 throw std::runtime_error(
"A48 record has no bucket values");
610 if (!bucket_count_set || scores->size() != expected_bucket_count) {
611 throw std::runtime_error(
"A48 data row width changed between records");
613 if (meta->len != 0U && meta->len != scores->size()) {
614 throw std::runtime_error(
"#len disagrees with the A48 data row width");
618 decoded.
metadata = std::move(*meta);
619 decoded.
scores = std::move(*scores);
620 result.
records[job.ordinal] = std::move(decoded);
623 errors[worker] = std::current_exception();
627 for (
auto& worker : workers) {
630 for (
auto const& error : errors) {
633 std::rethrow_exception(error);
634 }
catch (std::exception
const& exception) {
681 std::vector<uint16_t>
const& rows,
682 std::vector<uint16_t>
const& query,
684 uint32_t minimum_matches
686 auto const reference_count = rows.size() / bucket_count;
690 for (
size_t reference = 0; reference < reference_count; ++reference) {
692 auto const* reference_row = rows.data() + reference * bucket_count;
693 auto const* query_row = query.data();
694 for (
size_t bucket = 0; bucket < bucket_count; ++bucket) {
695 auto const query_score = query_row[bucket];
696 auto const reference_score = reference_row[bucket];
697 if (query_score == reference_score) {
698 if (query_score == 0U) {
699 ++summary.both_empty;
704 }
else if (query_score < reference_score) {
710 if (result.
match_counts[reference] >= minimum_matches) {
719 std::vector<std::vector<uint16_t>>
const& rows,
720 std::vector<uint16_t>
const& query,
721 uint32_t minimum_matches
723 std::vector<uint16_t> flat;
724 auto const bucket_count = rows.empty() ?
size_t{0} : rows.front().size();
725 flat.reserve(rows.size() * bucket_count);
726 for (
auto const& row : rows) {
727 flat.insert(flat.end(), row.begin(), row.end());
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
Result< T > parse_field(Parser &&parser, std::string_view value, char const *field)
Parses a header field with parser, converting any thrown exception into an error.
Result< record_metadata > parse_record_headers(std::string_view block)
Parses a record header block (one "#key\tvalue" per line) into record_metadata.
bool is_record_header(std::string_view key)
True when key is a record-level header (part of a record block) rather than a file-level header that ...
Result< database > decode_a48_tsv(std::string_view input)
Decodes an uncompressed A48-encoded DDL TSV into score rows and metadata.
std::string encode_a48_token(uint64_t value)
Encodes an integer as an A48 (base-64, ASCII-offset-48) token.
oracle_result exhaustive_oracle(std::vector< uint16_t > const &rows, std::vector< uint16_t > const &query, size_t bucket_count, uint32_t minimum_matches)
Exact match counts and pair summaries for one flat query row.
std::vector< std::string_view > split_fields(std::string_view text, char separator, bool keep_trailing_empty=false)
Splits text on separator.
Result< std::vector< uint16_t > > decode_a48_row(std::string_view row, record_metadata const &metadata, uint8_t exponent_bits)
Decodes one A48 data row into absolute 16-bit bucket scores.
std::optional< uint64_t > decode_a48_token(std::string_view token)
Decodes one A48 (base-64, ASCII-offset-48) token to its integer value.
uint16_t promote_score(uint16_t loaded, int32_t offset, uint8_t exponent_bits)
Reconstructs one absolute 16-bit bucket maxima from an A48-decoded value.
Result< database > decode_a48_tsv_parallel(std::string_view input, unsigned requested_threads=std::thread::hardware_concurrency())
Multithreaded variant of decode_a48_tsv.
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
static Error invalid_argument(std::string message)
Fallible API result: cuda::std::expected<T, Error> with cuDDL factories.
static Result ok(T value)
Fully decoded A48 reference database.
database_metadata metadata
std::vector< record > records
Independent host oracle for one query over a decoded reference database.
std::vector< row_summary > summaries
std::vector< uint32_t > match_counts
One decoded DDL score row plus its stable record identity and construction metadata.
std::vector< uint16_t > scores
Absolute 16-bit bucket maxima (maxArray) reconstructed from the A48-encoded values.
uint32_t ordinal
Stable record identity: the record's ordinal in the file, matching the BBTools DDLIndex clade IDs (th...
Exact bucket-classification counts for one decoded row pair.
friend bool operator==(row_summary const &, row_summary const &)=default