Skip to content

Conversation

JasMehta08
Copy link
Contributor

This Pull request:

This PR adds a new feature to RooUniform to support a 1D uniform distribution with fittable lower and upper bounds, addressing issue #7880.

Changes or fixes:

The RooUniform class is updated with a new constructor that accepts a single observable (x) and two RooAbsReal parameters for the lower (x_low) and upper (x_up) bounds.

The core methods (evaluate, getAnalyticalIntegral, analyticalIntegral, getGenerator, generateEvent) have been updated to handle both the new 1D bounded mode and the original N-dimensional legacy mode, ensuring full backward compatibility. The documentation in the header and source files has also been updated to reflect this new functionality.

Testing

The new feature was validated with a comprehensive test that compares the old, inflexible RooUniform with the new, bounded version in a realistic physics fit (signal peak on a flat background).

The test script, plot, and numerical results are shown below. The results confirm that the new RooUniform can successfully measure the boundaries of the background, leading to a statistically better fit and a more accurate measurement of the signal yield.

Test Script (comparison_test_numerical.cpp)

#include "RooRealVar.h"
#include "RooGaussian.h"
#include "RooUniform.h"
#include "RooAddPdf.h"
#include "RooDataSet.h"
#include "RooPlot.h"
#include "TCanvas.h"
#include "TAxis.h"
#include "RooFitResult.h"
#include "TLegend.h"
#include <iostream>
#include <memory>

using namespace RooFit;

void comparison_test_numerical() {
    RooRealVar x("x", "x", 0, 20);
    RooGaussian signal_truth("signal_truth", "signal_truth", x, RooConst(10.0), RooConst(0.5));
    RooRealVar bkg_low_truth("bkg_low_truth", "bkg_low_truth", 4.0);
    RooRealVar bkg_high_truth("bkg_high_truth", "bkg_high_truth", 16.0);
    RooUniform background_truth("background_truth", "background_truth", x, bkg_low_truth, bkg_high_truth);
    RooRealVar n_sig_truth("n_sig_truth", "n_sig_truth", 300);
    RooRealVar n_bkg_truth("n_bkg_truth", "n_bkg_truth", 700);
    RooAddPdf model_truth("model_truth", "model_truth", {signal_truth, background_truth}, {n_sig_truth, n_bkg_truth});
    std::unique_ptr<RooDataSet> data{model_truth.generate(x, 1000)};
    TCanvas* c = new TCanvas("c", "RooUniform Before vs. After", 1200, 600);
    c->Divide(2, 1);

    c->cd(1);
    RooPlot* frame_before = x.frame(Title("The Problem (Old RooUniform)"));
    data->plotOn(frame_before);
    RooUniform background_old("background_old", "background_old", x);
    RooRealVar n_sig_old("n_sig_old", "n_sig_old", 300, 0, 10000);
    RooRealVar n_bkg_old("n_bkg_old", "n_bkg_old", 700, 0, 10000);
    RooAddPdf model_old("model_old", "model_old", {signal_truth, background_old}, {n_sig_old, n_bkg_old});
    std::unique_ptr<RooFitResult> fitResult_old{model_old.fitTo(*data, Save(), PrintLevel(-1))};
    model_old.plotOn(frame_before, LineColor(kRed), Name("fit_old"));
    frame_before->Draw();

    c->cd(2);
    RooPlot* frame_after = x.frame(Title("The Solution (New RooUniform)"));
    data->plotOn(frame_after);
    RooRealVar bkg_low_fit("bkg_low_fit", "bkg_low_fit", 3.0, 0.0, 8.0);
    RooRealVar bkg_high_fit("bkg_high_fit", "bkg_high_fit", 17.0, 12.0, 20.0);
    RooUniform background_new("background_new", "background_new", x, bkg_low_fit, bkg_high_fit);
    RooRealVar n_sig_new("n_sig_new", "n_sig_new", 300, 0, 10000);
    RooRealVar n_bkg_new("n_bkg_new", "n_bkg_new", 700, 0, 10000);
    RooAddPdf model_new("model_new", "model_new", {signal_truth, background_new}, {n_sig_new, n_bkg_new});
    std::unique_ptr<RooFitResult> fitResult_new{model_new.fitTo(*data, Save(), PrintLevel(-1))};
    model_new.plotOn(frame_after, LineColor(kGreen+2), Name("fit_new"));
    model_new.plotOn(frame_after, Components("background_new"), LineStyle(kDashed), LineColor(kMagenta));
    frame_after->Draw();

    std::cout << "\n\n--- Numerical Comparison ---" << std::endl;
    std::cout << "\n--- Fit with OLD RooUniform (The Problem) ---" << std::endl;
    fitResult_old->Print();
    std::cout << "\n--- Fit with NEW RooUniform (The Solution) ---" << std::endl;
    fitResult_new->Print();

    c->SaveAs("comparison_test_numerical.png");
}

Visual and Numerical Results

The plot below shows a side-by-side comparison. The fit with the old RooUniform (left) fails to model the data correctly, while the fit with the new, bounded RooUniform (right) provides an excellent description.

comparison_test_numerical

The numerical results confirm this. The new fit has a better FCN value and correctly measures the background bounds, which was previously impossible.

Fit with OLD RooUniform (The Problem):

RooFitResult: minimized FCN value: -3275.15, estimated distance to minimum: 3.40535e-05
                covariance matrix quality: Full, accurate covariance matrix
                Status : MINIMIZE=0 HESSE=0 

    Floating Parameter    FinalValue +/-  Error   
  --------------------  --------------------------
             n_bkg_old    6.3466e+02 +/-  2.70e+01
             n_sig_old    3.6534e+02 +/-  2.14e+01

Fit with NEW RooUniform (The Solution):

  RooFitResult: minimized FCN value: -3617.42, estimated distance to minimum: 0.00183732
                covariance matrix quality: Full, accurate covariance matrix
                Status : MINIMIZE=-1 HESSE=3 

    Floating Parameter    FinalValue +/-  Error   
  --------------------  --------------------------
          bkg_high_fit    1.5947e+01 +/-  3.03e-09
           bkg_low_fit    4.0115e+00 +/-  1.04e-03
             n_bkg_new    6.9608e+02 +/-  2.91e+01
             n_sig_new    3.0391e+02 +/-  2.13e+01

The true number of signal events is 300 so the new 1D is much closer with the 303 estimate.

Checklist:

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

This PR fixes #7880

@guitargeek guitargeek self-assigned this Sep 1, 2025
@guitargeek guitargeek changed the title FEAT: RooFit - Add 1D bounded constructor to RooUniform [RF] Add 1D bounded constructor to RooUniform Sep 1, 2025
@JasMehta08 JasMehta08 force-pushed the feature-roouniform-set-bounds branch from e403903 to 52b25ef Compare September 1, 2025 12:47
@JasMehta08
Copy link
Contributor Author

JasMehta08 commented Sep 1, 2025

@guitargeek i had a thought about the old code,

While working on this feature, I noticed that the multi-dimensional analyticalIntegral and getGenerator functions use a bitmask that limits them to 31 observables. I understand this is a very rare edge case, but it could be modernized in the future by replacing the bitmask loop with a standard C++ range-based for loop.

I kept the original code in this PR to keep the changes focused on the new 1D bounded feature, but I wanted to share my observation.

@JasMehta08
Copy link
Contributor Author

JasMehta08 commented Sep 1, 2025

Hi @guitargeek ,

I looked into the test logs and the test which is failing is gtest-roofit-hs3-testRooFitHS3: the RooFitHS3.RooUniform sub-test is failing due to a serialization issue. The test's reference data, which was created with the old RooUniform class structure, is no longer compatible with the new version of the class that includes the members for the bounded mode (x_single, x_low, x_up).

other than that no test is failing, please let me know what should i do.

(other than that there are issues regarding the S3 authentication error which is related to the CI test directly not to the pull request.)

Copy link

github-actions bot commented Sep 1, 2025

Test Results

    19 files      19 suites   3d 15h 18m 15s ⏱️
 3 648 tests  3 645 ✅   0 💤  3 ❌
67 954 runs  67 832 ✅ 101 💤 21 ❌

For more details on these failures, see this check.

Results for commit 52b25ef.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[RF] Allow RooUniform to set lower and upper bounds (support)
2 participants