Skip to content

[hist] Seed the parameters of a pol1 fit when coordinate errors are used - #22967

Open
tekinertekin wants to merge 2 commits into
root-project:masterfrom
tekinertekin:hfit-initpolynom-13895
Open

[hist] Seed the parameters of a pol1 fit when coordinate errors are used#22967
tekinertekin wants to merge 2 commits into
root-project:masterfrom
tekinertekin:hfit-initpolynom-13895

Conversation

@tekinertekin

Copy link
Copy Markdown
Contributor

A pol1 fit of a TGraphErrors diverges when the x-errors dominate the y-errors and the true slope is negative. On exactly collinear data (y = 7 - x) the fit returns a slope with the wrong sign:

double x[3] = {1, 2, 3}, y[3] = {6, 5, 4};
double ex[3] = {1, 1, 1}, ey[3] = {0.1, 0.1, 0.1};
TGraphErrors gr(3, x, y, ex, ey);
TF1 pol("pol", "pol1", 0., 4.);
gr.Fit(&pol, "Q");        // p0 = -18010.8, p1 = +9007.3, expected 7 and -1

Cause

HFitImpl::Fit marks a polynomial as linear (special == 299 + npar), but a few lines further down coordinate errors switch the linear fitter off again:

if (fitdata->GetErrorType() == ROOT::Fit::BinData::kCoordError && fitdata->Opt().fCoordErrors)
   linear = false;

The polynomial therefore reaches the minimizer, and the seeding block that follows covers only gaus (100), 2D-gaus (110/112), landau (400/410) and expo (200) — there is no polynomial case. The fit starts from whatever parameters the function happens to carry, and from the defaults it converges into the wrong valley.

That the missing seed is the cause can be shown on the data above by varying only the starting point:

initial pol1 parameters result
defaults (fresh function) p0 = -18010.8, p1 = +9007.3
(7, -1), left by a previous plain-TGraph fit p0 = 7, p1 = -1
forced back to (0, 0) p0 = -28034.5, p1 = +14019.8

This also matches the observation in the issue that setting the initial parameters by hand is a working workaround.

Fix

ROOT::Fit::InitPolynom computes an unweighted least-squares line through the data and sets it as the starting point, following the existing InitExpo/InitGaus pattern (those are unweighted too — the aim is a rough starting point, not a solved fit). It is wired in with the same polynomial test already used above, special == 299 + npar; writing special >= 300 would also capture landau (400) and 2D-landau (410) and override their gaus seeding.

Only the first-degree case is seeded — InitPolynom returns unchanged for a higher degree, so pol2 and above keep their current behaviour. Generalising means changing that one function, with no change at the call site; happy to do it here if you would prefer.

Tests

Four cases in hist/hist/test/test_TGraphErrors_polFit.cxx: the diverging fit, the positive-slope fit that already converged from the defaults, the same data without coordinate errors, and the same data with EX0. The last three pass before the change as well, so they guard the paths the new seeding must not disturb.

Each case builds its own TF1 rather than using the global pol1: a pol1 fit performed earlier in the same process leaves good parameters behind and hides the bug entirely.

Verified locally with the fix (4/4 pass), and with only the call in HFitImpl.cxx removed (exactly the first case fails, the other three still pass). testTFractionFitter, testTH1 and testTGraphSorting also pass. git-clang-format against master is clean with clang-format 20.1.8.

Related: #22651 touched the same file for multithreaded TGraphErrors fits with x errors.

I used an AI tool to help with this change. It created the patch and the regression test, reproduced the bug, and ran the test suite and lint. I have reviewed, understood, and verified the change and take full responsibility for it.

Changes or fixes:

Fixes #13895.

Checklist:

  • tested changes locally
  • updated the docs (if necessary)

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

Test Results

    23 files      23 suites   3d 18h 19m 6s ⏱️
 3 882 tests  3 882 ✅ 0 💤 0 ❌
79 967 runs  79 967 ✅ 0 💤 0 ❌

Results for commit 0dd922e.

♻️ This comment has been updated with latest results.

@hageboeck hageboeck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hello, thanks for the contribution! I think that's a great idea, but could you check the suggestions below?

Furthermore, we typically split code changes and tests into different commits, so could you add a second commit that adds the test, please?

Comment thread hist/hist/src/HFitInterface.cxx Outdated
Comment thread hist/hist/src/HFitInterface.cxx Outdated
Coordinate errors turn the linear fitter off in HFitImpl.cxx, so a polN fit
went to the minimizer carrying whatever parameters the function already had.
From a starting point far from the solution, a line of negative slope could
converge with the sign reversed.

Add ROOT::Fit::InitPolynom, which seeds the fit from an unweighted
least-squares line, and call it from the same block that already seeds gaus,
expo and landau.
…rrors

Covers both slope signs with coordinate errors, the plain case without them,
and the case where ex is zero so the linear fitter stays on.
@tekinertekin
tekinertekin force-pushed the hfit-initpolynom-13895 branch from 28f0aa6 to 0dd922e Compare July 31, 2026 10:33
@tekinertekin

Copy link
Copy Markdown
Contributor Author

Hello, thanks for the contribution! I think that's a great idea, but could you check the suggestions below?

Furthermore, we typically split code changes and tests into different commits, so could you add a second commit that adds the test, please?

Thanks a lot for your reviews @hageboeck . All three addressed:

  • Split into two commits: the code change, then the test.
  • Renamed to sumX, sumY, sumXSq, sumXY.
  • Took your suggestion for the doc comment. I moved the explanation of why
    the seeding is needed (coordinate errors switch the linear fitter off, so the
    fit reaches the minimizer with whatever parameters the function carried) into
    the code commit message, so it stays on the record without sitting in the
    header.

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.

pol1 fit fails in TGraphErrors when x-errors>y-errors and slope<0

2 participants