cuDDL
Loading...
Searching...
No Matches
Namespaces | Classes | Functions
cuddl::a48 Namespace Reference

Namespaces

namespace  detail
 

Classes

struct  database
 Fully decoded A48 reference database. More...
 
struct  database_metadata
 Per-database construction metadata decoded from the A48 file-level header. More...
 
struct  oracle_result
 Independent host oracle for one query over a decoded reference database. More...
 
struct  record
 One decoded DDL score row plus its stable record identity and construction metadata. More...
 
struct  record_metadata
 Metadata attached to one decoded A48 record. More...
 
struct  row_summary
 Exact bucket-classification counts for one decoded row pair. More...
 

Functions

std::optional< uint64_t > decode_a48_token (std::string_view token)
 Decodes one A48 (base-64, ASCII-offset-48) token to its integer value.
 
std::string encode_a48_token (uint64_t value)
 Encodes an integer as an A48 (base-64, ASCII-offset-48) token.
 
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.
 
std::vector< std::string_view > split_fields (std::string_view text, char separator, bool keep_trailing_empty=false)
 Splits text on separator.
 
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 precedes every record.
 
Result< record_metadata > parse_record_headers (std::string_view block)
 Parses a record header block (one "#key\tvalue" per line) into record_metadata.
 
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.
 
Result< database > decode_a48_tsv (std::string_view input)
 Decodes an uncompressed A48-encoded DDL TSV into score rows and metadata.
 
Result< database > decode_a48_tsv_parallel (std::string_view input, unsigned requested_threads=std::thread::hardware_concurrency())
 Multithreaded variant of decode_a48_tsv.
 
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.
 
oracle_result exhaustive_oracle (std::vector< std::vector< uint16_t > > const &rows, std::vector< uint16_t > const &query, uint32_t minimum_matches)
 Convenience overload over decoded records instead of one flat score array.
 

Function Documentation

◆ decode_a48_row()

Result< std::vector< uint16_t > > cuddl::a48::decode_a48_row ( std::string_view  row,
record_metadata const &  metadata,
uint8_t  exponent_bits 
)
inline

Decodes one A48 data row into absolute 16-bit bucket scores.

Definition at line 264 of file a48.hpp.

264 {
265 auto const fields = split_fields(row, '\t', true);
266 std::vector<uint16_t> scores;
267 scores.reserve(fields.size());
268 for (auto const field : fields) {
269 auto const loaded = decode_a48_token(field);
270 if (!loaded) {
271 return Err(
272 Error::invalid_argument(
273 "A48 data row contains a character outside the A48 alphabet"
274 )
275 );
276 }
277 scores.push_back(
278 promote_score(static_cast<uint16_t>(*loaded), metadata.offset, exponent_bits)
279 );
280 }
281 return Result<std::vector<uint16_t>>::ok(std::move(scores));
282}
std::vector< std::string_view > split_fields(std::string_view text, char separator, bool keep_trailing_empty=false)
Splits text on separator.
Definition a48.hpp:136
std::optional< uint64_t > decode_a48_token(std::string_view token)
Decodes one A48 (base-64, ASCII-offset-48) token to its integer value.
Definition a48.hpp:84
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
Definition error.hpp:176
Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_a48_token()

std::optional< uint64_t > cuddl::a48::decode_a48_token ( std::string_view  token)
inline

Decodes one A48 (base-64, ASCII-offset-48) token to its integer value.

Returns std::nullopt when a character is outside the A48 alphabet (ASCII 48..111), i.e. the token is malformed.

Definition at line 84 of file a48.hpp.

84 {
85 if (token.empty()) {
86 return uint64_t{0};
87 }
88 uint64_t value = 0;
89 for (char const character : token) {
90 auto const code = static_cast<uint8_t>(character);
91 if (code < 48U || code > 111U) {
92 return std::nullopt;
93 }
94 value = (value << 6U) | static_cast<uint64_t>(code - 48U);
95 }
96 return value;
97}
Here is the caller graph for this function:

◆ decode_a48_tsv()

Result< database > cuddl::a48::decode_a48_tsv ( std::string_view  input)
inline

Decodes an uncompressed A48-encoded DDL TSV into score rows and metadata.

The decoder is intentionally strict about the structural invariants that would otherwise yield silent garbage: A48 alphabet violations, an omitted #len disagreeing with the data row, a bucket count that changes between records, an absent #exponent on a relative-encoded file, and records without any bucket values are all reported as malformed input. It does not, however, depend on the large official RefSeq asset, so a small deterministic fixture can exercise the code paths in unit tests.

Definition at line 292 of file a48.hpp.

292 {
293 database result;
294 uint8_t exponent_bits = 6U;
295 bool has_exponent = false;
296 size_t expected_bucket_count = 0;
297 bool bucket_count_set = false;
298
299 std::string header_block;
300 size_t ordinal = 0;
301 size_t begin = 0;
302 while (begin <= input.size()) {
303 auto const end = input.find('\n', begin);
304 auto line =
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);
308 }
309
310 if (line.empty()) {
311 if (end == std::string_view::npos) {
312 break;
313 }
314 begin = end + 1U;
315 continue;
316 }
317
318 if (line.front() == '#') {
319 auto const fields = split_fields(line, '\t');
320 if (fields.empty()) {
321 break;
322 }
323 auto key = fields.front();
324 key.remove_prefix(1); // drop '#'
325 auto const value = fields.size() > 1U ? fields[1] : std::string_view{};
326
327 if (key == "k") {
328 auto const parsed = detail::parse_field<uint32_t>(
329 [](std::string const& s) { return std::stoul(s); }, value, "k"
330 );
331 if (!parsed) {
332 return Err(parsed.error());
333 }
334 result.metadata.kmer_length = *parsed;
335 result.metadata.has_kmer_length = true;
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"
339 );
340 if (!parsed) {
341 return Err(parsed.error());
342 }
343 result.metadata.seed = *parsed;
344 result.metadata.has_seed = true;
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"
348 );
349 if (!parsed) {
350 return Err(parsed.error());
351 }
352 if (*parsed < 1U || *parsed > 15U) {
353 return Err(Error::invalid_argument("A48 exponent must be in [1, 15]"));
354 }
355 exponent_bits = *parsed;
356 result.metadata.exponent_bits = *parsed;
357 result.metadata.has_exponent = true;
358 has_exponent = true;
359 } else if (key == "blacklist") {
360 result.metadata.blacklist = std::string(value);
361 }
362
363 if (is_record_header(key)) {
364 if (!header_block.empty()) {
365 header_block.push_back('\n');
366 }
367 header_block.append(line);
368 }
369
370 if (end == std::string_view::npos) {
371 break;
372 }
373 begin = end + 1U;
374 continue;
375 }
376
377 // A data row terminates the currently accumulated record header block.
378 if (header_block.empty()) {
379 return Err(Error::invalid_argument("A48 data row appears before any record header"));
380 }
381 auto meta = CUDDL_TRY(parse_record_headers(header_block));
382 header_block.clear();
383
384 if (meta.offset >= 0 && !has_exponent) {
385 return Err(
386 Error::invalid_argument("relative-encoded A48 record requires a #exponent header")
387 );
388 }
389 auto scores = CUDDL_TRY(decode_a48_row(line, meta, exponent_bits));
390 if (scores.empty()) {
391 return Err(Error::invalid_argument("A48 record has no bucket values"));
392 }
393 if (!bucket_count_set) {
394 expected_bucket_count = scores.size();
395 bucket_count_set = true;
396 } else if (scores.size() != expected_bucket_count) {
397 return Err(Error::invalid_argument("A48 data row width changed between records"));
398 }
399 if (meta.len != 0U && meta.len != scores.size()) {
400 return Err(Error::invalid_argument("#len disagrees with the A48 data row width"));
401 }
402
403 record decoded;
404 decoded.ordinal = static_cast<uint32_t>(ordinal);
405 decoded.metadata = meta;
406 decoded.scores = std::move(scores);
407 result.records.push_back(std::move(decoded));
408 ++ordinal;
409
410 if (end == std::string_view::npos) {
411 break;
412 }
413 begin = end + 1U;
414 }
415
416 if (!header_block.empty()) {
417 return Err(Error::invalid_argument("A48 file ends inside a record header block"));
418 }
419 if (result.records.empty()) {
420 return Err(Error::invalid_argument("A48 file contains no records"));
421 }
422
423 result.metadata.record_count = result.records.size();
424 return Result<database>::ok(std::move(result));
425}
#define CUDDL_TRY(expr)
Propagates a cuddl::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:225
Result< record_metadata > parse_record_headers(std::string_view block)
Parses a record header block (one "#key\tvalue" per line) into record_metadata.
Definition a48.hpp:177
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 ...
Definition a48.hpp:170
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.
Definition a48.hpp:264
size_t record_count
Number of decoded records.
Definition a48.hpp:35
std::string blacklist
Blacklist filenames recorded in the header (#blacklist), if any.
Definition a48.hpp:33
uint8_t exponent_bits
Exponent bit width (#exponent) of each 16-bit bucket value.
Definition a48.hpp:30
uint32_t kmer_length
K-mer length (#k). Present when the file was written with an explicit k.
Definition a48.hpp:23
uint64_t seed
Hash seed (#seed). Present when the file was written with a nonzero seed.
Definition a48.hpp:26
Fully decoded A48 reference database.
Definition a48.hpp:75
database_metadata metadata
Definition a48.hpp:76
std::vector< record > records
Definition a48.hpp:77
Here is the call graph for this function:

◆ decode_a48_tsv_parallel()

Result< database > cuddl::a48::decode_a48_tsv_parallel ( std::string_view  input,
unsigned  requested_threads = std::thread::hardware_concurrency() 
)
inline

Multithreaded variant of decode_a48_tsv.

One pass discovers file-level metadata, record header blocks, and data-row spans. The first record is decoded synchronously to establish the bucket width; the remaining independent records are decoded by a fixed pool and written directly to their ordinal slots, so order and strict validation are identical to the single-threaded decoder.

requested_threads defaults to std::thread::hardware_concurrency().

One undecoded record: its header block, data row, and file ordinal.

Definition at line 435 of file a48.hpp.

438 {
440 struct record_job {
441 std::string header;
442 std::string_view data;
443 uint32_t ordinal{};
444 };
445
446 database result;
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;
452
453 std::string header_block;
454 size_t ordinal = 0;
455 size_t begin = 0;
456 while (begin <= input.size()) {
457 auto const end = input.find('\n', begin);
458 auto line =
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);
462 }
463 if (line.empty()) {
464 if (end == std::string_view::npos) {
465 break;
466 }
467 begin = end + 1U;
468 continue;
469 }
470
471 if (line.front() == '#') {
472 auto const fields = split_fields(line, '\t');
473 if (fields.empty()) {
474 break;
475 }
476 auto key = fields.front();
477 key.remove_prefix(1);
478 auto const value = fields.size() > 1U ? fields[1] : std::string_view{};
479 if (key == "k") {
480 auto const parsed = detail::parse_field<uint32_t>(
481 [](std::string const& s) { return std::stoul(s); }, value, "k"
482 );
483 if (!parsed) {
484 return Err(parsed.error());
485 }
486 result.metadata.kmer_length = *parsed;
487 result.metadata.has_kmer_length = true;
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"
491 );
492 if (!parsed) {
493 return Err(parsed.error());
494 }
495 result.metadata.seed = *parsed;
496 result.metadata.has_seed = true;
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"
500 );
501 if (!parsed) {
502 return Err(parsed.error());
503 }
504 if (*parsed < 1U || *parsed > 15U) {
505 return Err(Error::invalid_argument("A48 exponent must be in [1, 15]"));
506 }
507 exponent_bits = *parsed;
508 result.metadata.exponent_bits = *parsed;
509 result.metadata.has_exponent = true;
510 has_exponent = true;
511 } else if (key == "blacklist") {
512 result.metadata.blacklist = std::string(value);
513 }
514
515 if (is_record_header(key)) {
516 if (!header_block.empty()) {
517 header_block.push_back('\n');
518 }
519 header_block.append(line);
520 }
521 if (end == std::string_view::npos) {
522 break;
523 }
524 begin = end + 1U;
525 continue;
526 }
527
528 if (header_block.empty()) {
529 return Err(Error::invalid_argument("A48 data row appears before any record header"));
530 }
531 auto const record_ordinal = static_cast<uint32_t>(ordinal);
532 if (record_ordinal == 0U) {
533 auto meta = CUDDL_TRY(parse_record_headers(header_block));
534 header_block.clear();
535 if (meta.offset >= 0 && !has_exponent) {
536 return Err(
537 Error::invalid_argument(
538 "relative-encoded A48 record requires a #exponent header"
539 )
540 );
541 }
542 auto scores = CUDDL_TRY(decode_a48_row(line, meta, exponent_bits));
543 if (scores.empty()) {
544 return Err(Error::invalid_argument("A48 record has no bucket values"));
545 }
546 expected_bucket_count = scores.size();
547 bucket_count_set = true;
548 if (meta.len != 0U && meta.len != scores.size()) {
549 return Err(Error::invalid_argument("#len disagrees with the A48 data row width"));
550 }
551 record decoded;
552 decoded.ordinal = record_ordinal;
553 decoded.metadata = std::move(meta);
554 decoded.scores = std::move(scores);
555 result.records.push_back(std::move(decoded));
556 } else {
557 jobs.push_back(
558 record_job{std::move(header_block), line, static_cast<uint32_t>(ordinal)}
559 );
560 header_block.clear();
561 }
562 ++ordinal;
563
564 if (end == std::string_view::npos) {
565 break;
566 }
567 begin = end + 1U;
568 }
569
570 if (!header_block.empty()) {
571 return Err(Error::invalid_argument("A48 file ends inside a record header block"));
572 }
573 if (result.records.empty()) {
574 return Err(Error::invalid_argument("A48 file contains no records"));
575 }
576
577 result.records.resize(result.records.size() + jobs.size());
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);
583
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) {
588 continue;
589 }
590 workers.emplace_back([&, chunk_begin, chunk_end, worker]() {
591 try {
592 for (size_t i = chunk_begin; i < chunk_end; ++i) {
593 auto const& job = jobs[i];
594 auto meta = parse_record_headers(job.header);
595 if (!meta) {
596 throw std::runtime_error(meta.error().message());
597 }
598 if (meta->offset >= 0 && !has_exponent) {
599 throw std::runtime_error(
600 "relative-encoded A48 record requires a #exponent header"
601 );
602 }
603 auto scores = decode_a48_row(job.data, *meta, exponent_bits);
604 if (!scores) {
605 throw std::runtime_error(scores.error().message());
606 }
607 if (scores->empty()) {
608 throw std::runtime_error("A48 record has no bucket values");
609 }
610 if (!bucket_count_set || scores->size() != expected_bucket_count) {
611 throw std::runtime_error("A48 data row width changed between records");
612 }
613 if (meta->len != 0U && meta->len != scores->size()) {
614 throw std::runtime_error("#len disagrees with the A48 data row width");
615 }
616 record decoded;
617 decoded.ordinal = job.ordinal;
618 decoded.metadata = std::move(*meta);
619 decoded.scores = std::move(*scores);
620 result.records[job.ordinal] = std::move(decoded);
621 }
622 } catch (...) {
623 errors[worker] = std::current_exception();
624 }
625 });
626 }
627 for (auto& worker : workers) {
628 worker.join();
629 }
630 for (auto const& error : errors) {
631 if (error) {
632 try {
633 std::rethrow_exception(error);
634 } catch (std::exception const& exception) {
635 return Err(Error::invalid_argument(exception.what()));
636 }
637 }
638 }
639
640 result.metadata.record_count = result.records.size();
641 return Result<database>::ok(std::move(result));
642}
Here is the call graph for this function:

◆ encode_a48_token()

std::string cuddl::a48::encode_a48_token ( uint64_t  value)
inline

Encodes an integer as an A48 (base-64, ASCII-offset-48) token.

Definition at line 100 of file a48.hpp.

100 {
101 if (value == 0U) {
102 return std::string(1, '0');
103 }
104 std::string reversed;
105 while (value != 0U) {
106 reversed.push_back(static_cast<char>(48U + (value & 0x3fU)));
107 value >>= 6U;
108 }
109 return {reversed.rbegin(), reversed.rend()};
110}

◆ exhaustive_oracle() [1/2]

oracle_result cuddl::a48::exhaustive_oracle ( std::vector< std::vector< uint16_t > > const &  rows,
std::vector< uint16_t > const &  query,
uint32_t  minimum_matches 
)
inline

Convenience overload over decoded records instead of one flat score array.

Definition at line 718 of file a48.hpp.

722 {
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());
728 }
729 return exhaustive_oracle(flat, query, bucket_count, minimum_matches);
730}
Here is the call graph for this function:

◆ exhaustive_oracle() [2/2]

oracle_result cuddl::a48::exhaustive_oracle ( std::vector< uint16_t > const &  rows,
std::vector< uint16_t > const &  query,
size_t  bucket_count,
uint32_t  minimum_matches 
)
inline

Exact match counts and pair summaries for one flat query row.

Definition at line 680 of file a48.hpp.

685 {
686 auto const reference_count = rows.size() / bucket_count;
687 oracle_result result;
688 result.match_counts.assign(reference_count, 0U);
689 result.summaries.assign(reference_count, row_summary{});
690 for (size_t reference = 0; reference < reference_count; ++reference) {
691 auto summary = row_summary{};
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;
700 } else {
701 ++summary.equal;
702 ++result.match_counts[reference];
703 }
704 } else if (query_score < reference_score) {
705 ++summary.lower;
706 } else {
707 ++summary.higher;
708 }
709 }
710 if (result.match_counts[reference] >= minimum_matches) {
711 result.summaries[reference] = summary;
712 }
713 }
714 return result;
715}
Independent host oracle for one query over a decoded reference database.
Definition a48.hpp:674
std::vector< row_summary > summaries
Definition a48.hpp:676
std::vector< uint32_t > match_counts
Definition a48.hpp:675
Exact bucket-classification counts for one decoded row pair.
Definition a48.hpp:645
Here is the caller graph for this function:

◆ is_record_header()

bool cuddl::a48::is_record_header ( std::string_view  key)
inline

True when key is a record-level header (part of a record block) rather than a file-level header that precedes every record.

Definition at line 170 of file a48.hpp.

170 {
171 return key == "id" || key == "tid" || key == "name" || key == "file" || key == "bases" ||
172 key == "contigs" || key == "gc" || key == "origin" || key == "lineage" || key == "len" ||
173 key == "offset";
174}
Here is the caller graph for this function:

◆ parse_record_headers()

Result< record_metadata > cuddl::a48::parse_record_headers ( std::string_view  block)
inline

Parses a record header block (one "#key\tvalue" per line) into record_metadata.

Definition at line 177 of file a48.hpp.

177 {
178 record_metadata meta;
179 for (auto const line : split_fields(block, '\n')) {
180 if (line.empty() || line.front() != '#') {
181 continue;
182 }
183 auto const fields = split_fields(line, '\t');
184 if (fields.empty()) {
185 continue;
186 }
187 auto key = fields.front();
188 key.remove_prefix(1); // drop '#'
189 if (fields.size() < 2U) {
190 continue;
191 }
192 auto const value = fields[1];
193 if (key == "id") {
194 auto const parsed = detail::parse_field<int64_t>(
195 [](std::string const& s) { return std::stoll(s); }, value, "id"
196 );
197 if (!parsed) {
198 return Err(parsed.error());
199 }
200 meta.id = *parsed;
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"
204 );
205 if (!parsed) {
206 return Err(parsed.error());
207 }
208 meta.tax_id = *parsed;
209 } else if (key == "name") {
210 meta.name = std::string(value);
211 } else if (key == "file") {
212 meta.filename = std::string(value);
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"
216 );
217 if (!parsed) {
218 return Err(parsed.error());
219 }
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"
224 );
225 if (!parsed) {
226 return Err(parsed.error());
227 }
228 meta.contigs = *parsed;
229 } else if (key == "gc") {
230 auto const parsed = detail::parse_field<float>(
231 [](std::string const& s) { return std::stof(s); }, value, "gc"
232 );
233 if (!parsed) {
234 return Err(parsed.error());
235 }
236 meta.gc = *parsed;
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"
244 );
245 if (!parsed) {
246 return Err(parsed.error());
247 }
248 meta.len = *parsed;
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"
252 );
253 if (!parsed) {
254 return Err(parsed.error());
255 }
256 meta.offset = *parsed;
257 }
258 }
259 return Result<record_metadata>::ok(std::move(meta));
260}
Metadata attached to one decoded A48 record.
Definition a48.hpp:39
std::string lineage
Lineage (#lineage).
Definition a48.hpp:57
int64_t bases
Number of bases (#bases); -1 when omitted.
Definition a48.hpp:49
uint32_t len
Bucket count recorded by #len; 0 when omitted.
Definition a48.hpp:59
std::string origin
Origin (#origin).
Definition a48.hpp:55
float gc
GC fraction (#gc); negative when omitted.
Definition a48.hpp:53
std::string name
Organism/record name (#name).
Definition a48.hpp:45
std::string filename
Source filename (#file).
Definition a48.hpp:47
int32_t contigs
Number of contigs (#contigs); -1 when omitted.
Definition a48.hpp:51
int64_t id
First-observed sequence ID (#id); -1 when omitted.
Definition a48.hpp:41
int32_t offset
Global NLZ offset (#offset); -1 when omitted (legacy absolute-encoded rows).
Definition a48.hpp:61
int64_t tax_id
Taxonomy ID (#tid); -1 when unknown.
Definition a48.hpp:43
Here is the call graph for this function:
Here is the caller graph for this function:

◆ promote_score()

uint16_t cuddl::a48::promote_score ( uint16_t  loaded,
int32_t  offset,
uint8_t  exponent_bits 
)
inline

Reconstructs one absolute 16-bit bucket maxima from an A48-decoded value.

Matches BBTools' DynamicDemiLog.fromArray: when offset is >= 0 the stored value is relative to the record's global NLZ and must be promoted back into the full 16-bit space; when offset is -1 the stored value is already an absolute score. exponent_bits selects the split between the exponent (NLZ tier) and mantissa fields.

Definition at line 119 of file a48.hpp.

119 {
120 if (loaded == 0U || offset < 0) {
121 return loaded;
122 }
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));
128}
Here is the caller graph for this function:

◆ split_fields()

std::vector< std::string_view > cuddl::a48::split_fields ( std::string_view  text,
char  separator,
bool  keep_trailing_empty = false 
)
inline

Splits text on separator.

With keep_trailing_empty the split is exactly BBTools' LineParser1 term layout: a line ending in a separator produces one final empty field, which parseLongA48 decodes as zero. Header parsing drops that field instead (a trailing empty value is not a distinct header).

Definition at line 136 of file a48.hpp.

136 {
137 std::vector<std::string_view> fields;
138 size_t begin = 0;
139 while (true) {
140 auto const end = text.find(separator, begin);
141 if (end == std::string_view::npos) {
142 fields.push_back(text.substr(begin));
143 break;
144 }
145 fields.push_back(text.substr(begin, end - begin));
146 begin = end + 1U;
147 }
148 if (!keep_trailing_empty && !fields.empty() && fields.back().empty()) {
149 fields.pop_back();
150 }
151 return fields;
152}
Here is the caller graph for this function: