Skip to content

[hist] Improve limits of THLimitsFinder. #19605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

hageboeck
Copy link
Member

THLimitsFinder sometimes removes the first/last bin of an axis range. Here, it is ensured that the min and max of a buffered range is part of the axis range, and not removed by accident.
This fixes issues with TTree::Draw such as the one described in https://root-forum.cern.ch/t/bug-or-feature-in-ttree-draw/62862/

This supersedes #17689

@hageboeck hageboeck self-assigned this Aug 11, 2025
@hageboeck hageboeck added the skip ci Skip the full builds on the actions runners label Aug 11, 2025
Copy link

github-actions bot commented Aug 11, 2025

Test Results

    21 files      21 suites   3d 12h 18m 41s ⏱️
 3 391 tests  3 301 ✅  0 💤  90 ❌
69 490 runs  68 602 ✅ 29 💤 859 ❌

For more details on these failures, see this check.

Results for commit 99a5fe0.

♻️ This comment has been updated with latest results.

Copy link
Collaborator

@ferdymercury ferdymercury left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks!

I would propose this test:

TTree t;
Float_t x;
t.Branch("x", &x);
x = -999;
t.Fill();
x = 0;
t.Fill();
t.Draw("x");
auto h = (TH1*)gROOT->FindObject("htemp");
ASSERT_EQ(h->GetEntries(), h->GetEffectiveEntries());

@hageboeck hageboeck removed the skip ci Skip the full builds on the actions runners label Aug 13, 2025
@hageboeck hageboeck marked this pull request as ready for review August 13, 2025 15:06
@hageboeck
Copy link
Member Author

LGTM thanks!

I would propose this test:

TTree t;
Float_t x;
t.Branch("x", &x);
x = -999;
t.Fill();
x = 0;
t.Fill();
t.Draw("x");
auto h = (TH1*)gROOT->FindObject("htemp");
ASSERT_EQ(h->GetEntries(), h->GetEffectiveEntries());

@ferdymercury good test! It's added now.

Copy link
Collaborator

@ferdymercury ferdymercury left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Just a nitpick at the end of DrawAutoBinning:

delete h;
delete gROOT->FindObject("c1");

@hageboeck
Copy link
Member Author

Thanks! Just a nitpick at the end of DrawAutoBinning:

delete h;
delete gROOT->FindObject("c1");

👍, updated for h. The canvas isn't created in batch mode, though, so that should be be unnecessary in this test.

@ferdymercury
Copy link
Collaborator

The canvas isn't created in batch mode, though, so that should be be unnecessary in this test.

Are you sure?

root -l -b
root [0] TTree t;
root [1] Float_t x;
root [2] t.Branch("x", &x);
root [3] x = -999;
root [4] t.Fill();
root [5] x = 0;
root [6] t.Fill();
root [7] t.Draw("x");
Info in <TCanvas::MakeDefCanvas>:  created default TCanvas with name c1
root [8] gROOT->FindObject("c1")
(TObject *) 0x5c740608b9c0
root [9] delete gROOT->FindObject("c1")
root [10] gROOT->FindObject("c1")
(TObject *) nullptr

@hageboeck
Copy link
Member Author

The test failures for the graphics are real. This will require more investigation.

Comment on lines 340 to 349
if (al - BinLow >= atest && al < BinLow + BinWidth) {
// Suppress the first bin, but only if al doesn't fall into it
BinLow += BinWidth;
nbins--;
}
if (BinHigh - ah >= atest && BinHigh - BinWidth > ah) {
// Suppress the last bin, but only if ah doesn't fall into it
BinHigh -= BinWidth;
nbins--;
}
Copy link
Collaborator

@ferdymercury ferdymercury Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be al > BinLow + BinWidth ? instead of < ?
Or maybe even easier:

Suggested change
if (al - BinLow >= atest && al < BinLow + BinWidth) {
// Suppress the first bin, but only if al doesn't fall into it
BinLow += BinWidth;
nbins--;
}
if (BinHigh - ah >= atest && BinHigh - BinWidth > ah) {
// Suppress the last bin, but only if ah doesn't fall into it
BinHigh -= BinWidth;
nbins--;
}
if (al >= BinLow + BinWidth + atest) {
// Suppress the first bin, but only if al doesn't fall into it
BinLow += BinWidth;
nbins--;
}
if (ah <= BinHigh - BinWidth - atest) {
// Suppress the last bin, but only if ah doesn't fall into it
BinHigh -= BinWidth;
nbins--;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed.
But looking at this logic again, it's clear that it may truncate the axis range, so one shouldn't use it. At the same time, I cannot remove it (I tested), because this breaks the "looks" of other graphics components.
I think I now found a solution that works. It's already pushed.

@hageboeck hageboeck force-pushed the limits_finder branch 2 times, most recently from f81d9b6 to 6397529 Compare August 18, 2025 08:56
hageboeck and others added 3 commits August 18, 2025 15:26
During the merge of histograms with automatic axis ranges, the axis
range was computed when the default fill buffer overflows. In this case,
this was halfway during the merge.
The resulting histogram was compared to one whose axis range was
computed on the full statistics, leading to slightly different axes.

By chance, the differences were within the tolerance, but due to the
axis range being extended slightly in the next commit, the test started
failing.

To avoid this, the buffer of the first histogram to be merged is like for
the others in this test, ensuring that all histograms compute their axes
on the same events.
- When asking THLimitsFinder to optimise axis limits, ask for a
  maximum slightly right of the actual maximum of the distribution.
  Otherwise, since the maximum may coincide with the maximum of the
  axis range, values may fall into the overflow.
- Use std::min / std::max to ensure that OptimizeLimits never drops a
  value.

This fixes the problem observed in
https://root-forum.cern.ch/t/bug-or-feature-in-ttree-draw/62862
THLimitFinder tries to trim empty bins close to the end of an axis range,
but sometimes, the min/max was trimmed as well, so not all data would be
visible in the histogram.

Here, the case from the following post is tested:
https://root-forum.cern.ch/t/bug-or-feature-in-ttree-draw/

Co-authored-by: ferdymercury <[email protected]>
@hageboeck hageboeck added the clean build Ask CI to do non-incremental build on PR label Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clean build Ask CI to do non-incremental build on PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants