Skip to content

fix: report all levels and handle empty user bins in hibf_statistics - #349

Merged
eseiler merged 3 commits into
seqan:mainfrom
eseiler:fix/stats
Oct 12, 2026
Merged

eseiler merged 3 commits into
seqan:mainfrom
eseiler:fix/stats

Conversation

@eseiler

@eseiler eseiler commented Oct 12, 2026

Copy link
Copy Markdown
Member

I don't think all the cases where we could divide by zero could occur in the wild, but it's also not worth the time to disprove :)


Fixes two bugs in hibf_statistics, the summary printed with --output-verbose-statistics and
written by --determine-best-tmax.

IBFs below level 1 were dropped. collect_bins copied each child IBF into its merged bin while
iterating in max_bins order. Both layout algorithms sort max_bins by level, so a parent copied its
child before the child's own merged bins were attached. Every IBF on level 2 or deeper was reported as
empty (0 TBs, 0 bytes, NaN split percentage), and its size and query cost were missing from size
and l_tmax. Child IBFs are now attached top-down, independent of the order of max_bins.

  • New test hibf_statistics.three_levels, run with max_bins in both orders.
  • execute_test.chopper_layout_statistics_fast_layout encoded the bug. Its expectation changes:
    level 2 goes from 0 to 64 TBs, l_tmax from 1.24 to 1.29, and size from 672.6KiB to 685.3KiB.
    On main, this test fails in Debug builds because UBSan reports the 0/0.

Empty user bins caused 0/0. Three values were NaN:

  • avg_factor of a level whose split bins hold no k-mers,
  • l_tmax if all user bins are empty,
  • m_tmax if both the HIBF and the 64-HIBF it is compared to have size 0.

With all user bins empty and --disable-estimate-union, --determine-best-tmax never accepted a
tmax, and execute threw "The layout contains no user bins". These three now print -, 0.00 and
1.00.

  • New test hibf_statistics.empty_user_bins.

The NaN special case in to_string_with_precision can no longer be reached and is removed.

Not in this PR: with union estimation enabled (the default), empty user bins make hibf divide by zero
(sketch/toolbox.cpp:223), and execute can crash with std::out_of_range.

🤖 Generated with Claude Code

eseiler and others added 3 commits October 12, 2026 14:29
collect_bins copied each child IBF into its merged bin while iterating over the IBFs in max_bins
order. Both layout algorithms sort max_bins by level, so a parent copied its child before the
child's own merged bins were attached. Every IBF below level 1 was therefore reported as empty:
its size and query cost were missing from the summary, and its split_tb_percentage was 0/0, which
UBSan reports in Debug builds.

Attach the child IBFs top-down instead, recursing into each attached child. This does not depend
on the order of max_bins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
User bins without k-mers made three statistics divide 0 by 0:
* avg_factor of a level whose split bins are all empty,
* l_tmax (expected_HIBF_query_cost) if all user bins are empty,
* m_tmax if both the HIBF and the 64-HIBF it is compared to have a size of 0.

They were printed as NaN or -nan, and UBSan reports them in Debug builds. If all user bins were
empty, determine_best_tmax never accepted a tmax (NaN < best is false), and execute threw
"The layout contains no user bins".

Print "-" for such an avg_factor, as for levels without split bins. Use l_tmax = 0 (there are no
k-mers to query) and m_tmax = 1 (both have the same size).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No value printed with it can be NaN anymore: split_tb_percentage divides by the number of technical
bins on the level (at least 1), avg_split_tb is only computed if the level has split bins, and
avg_factor only if they contain k-mers. This also removes the trailing return type that was only
needed for the "NaN" string literal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@seqan-actions seqan-actions added lint and removed lint labels Oct 12, 2026
@seqan-actions

Copy link
Copy Markdown
Member

Documentation preview available at https://docs.seqan.de/preview/seqan/chopper/349

@codecov

codecov Bot commented Oct 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.68%. Comparing base (eac00f9) to head (c9a175a).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #349      +/-   ##
==========================================
+ Coverage   94.60%   94.68%   +0.07%     
==========================================
  Files          25       25              
  Lines        1297     1297              
  Branches       21       20       -1     
==========================================
+ Hits         1227     1228       +1     
+ Misses         70       69       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@eseiler
eseiler enabled auto-merge October 12, 2026 12:56
@eseiler
eseiler disabled auto-merge October 12, 2026 13:00
@eseiler
eseiler merged commit cea5b6d into seqan:main Oct 12, 2026
28 checks passed
@eseiler
eseiler deleted the fix/stats branch October 12, 2026 13:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants