Skip to content

Commit 28b3942

Browse files
committed
[RF] Fix implementation of RooGenericPdf/RooFormulaVar::binBoundaries()
Follows up on 9ea4ec6, which got the implementation of `RooFormulaVar::binBoundaries()` and `RooGenericPdf::binBoundaries()` wrong. The limis passed to the `binBoundaries()` call should not be included in the returned `std::list<double>`. I was led to believe that including the query boundaries was necessary, because otherwise the numeric integrals didn't work, but I see now that this is actually a bug on the RooBinIntegrator side, which is now reported as #22858. Also, implement a unit test that ensures the fixed implementation of `binBoundaries()` is consistent with the one of RooHistPdf and matches the expected values. Finally, move a remaining helper that was added in 9ea4ec6 from the public to the private interface (from RooHelpers.h to RooFitImplHelpers.h, which never gets installed).
1 parent 6aa9ab4 commit 28b3942

7 files changed

Lines changed: 113 additions & 88 deletions

File tree

roofit/roofitcore/inc/RooHelpers.h

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,13 @@
1717
#include <RooAbsArg.h>
1818
#include <RooAbsReal.h>
1919

20-
#include <ROOT/RSpan.hxx>
21-
2220
#include <sstream>
23-
#include <list>
2421
#include <vector>
2522
#include <string>
2623
#include <utility>
2724

2825
class RooAbsPdf;
2926
class RooAbsData;
30-
class RooAbsRealLValue;
3127

3228
namespace RooHelpers {
3329

@@ -95,22 +91,6 @@ void checkRangeOfParameters(const RooAbsReal *callingClass, std::initializer_lis
9591
/// set all RooRealVars to constants. return true if at least one changed status
9692
bool setAllConstant(const RooAbsCollection &coll, bool constant = true);
9793

98-
/// Check that `function` is constant (flat) inside each bin defined by the
99-
/// sorted `boundaries` when scanning the observable `obs`. Several interior
100-
/// points are sampled per bin and compared to the bin's first sample; if any
101-
/// of them deviates by more than `relTol` (relative to the value scale), the
102-
/// function is not flat and false is returned. The value of `obs` is restored
103-
/// on return.
104-
bool isFunctionFlatInBins(const RooAbsReal &function, RooAbsRealLValue &obs, std::span<const double> boundaries,
105-
double relTol = 1e-9);
106-
107-
/// Return a newly allocated list with the subset of `boundaries` that lies
108-
/// strictly inside [`xlo`, `xhi`], with `xlo` and `xhi` added as the first and
109-
/// last entries. This is the form expected by RooFit's binBoundaries()
110-
/// interface, so the bin integrator covers exactly the integration range.
111-
/// The caller takes ownership of the returned list.
112-
std::list<double> *binBoundariesInRange(std::span<const double> boundaries, double xlo, double xhi);
113-
11494
} // namespace RooHelpers
11595

11696
#endif

roofit/roofitcore/res/RooFitImplHelpers.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ BinnedLOutput getBinnedL(RooAbsPdf const &pdf);
127127

128128
void getSortedComputationGraph(RooAbsArg const &func, RooArgSet &out);
129129

130+
/// Check that `function` is constant (flat) inside each bin defined by the
131+
/// sorted `boundaries` when scanning the observable `obs`. Several interior
132+
/// points are sampled per bin and compared to the bin's first sample; if any
133+
/// of them deviates by more than `relTol` (relative to the value scale), the
134+
/// function is not flat and false is returned. The value of `obs` is restored
135+
/// on return.
136+
bool isFunctionFlatInBins(const RooAbsReal &function, RooAbsRealLValue &obs, std::span<const double> boundaries,
137+
double relTol = 1e-9);
138+
130139
} // namespace RooHelpers
131140

132141
namespace RooFit::Detail {

roofit/roofitcore/src/RooFitImplHelpers.cxx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,42 @@ RooAbsArg *cloneTreeWithSameParametersImpl(RooAbsArg const &arg, RooArgSet const
286286

287287
} // namespace Detail
288288

289+
bool isFunctionFlatInBins(const RooAbsReal &function, RooAbsRealLValue &obs, std::span<const double> boundaries,
290+
double relTol)
291+
{
292+
// Fractions of the bin width at which the function is sampled. They are kept
293+
// strictly inside the bin (away from the boundaries) so that the evaluation
294+
// is not affected by which side of a boundary a step function jumps.
295+
const double fractions[] = {0.04, 0.27, 0.5, 0.73, 0.96};
296+
297+
const double savedVal = obs.getVal();
298+
299+
bool isFlat = true;
300+
for (std::size_t i = 0; i + 1 < boundaries.size() && isFlat; ++i) {
301+
const double lo = boundaries[i];
302+
const double hi = boundaries[i + 1];
303+
double reference = 0.0;
304+
bool first = true;
305+
for (double frac : fractions) {
306+
obs.setVal(lo + frac * (hi - lo));
307+
const double val = function.getVal();
308+
if (first) {
309+
reference = val;
310+
first = false;
311+
continue;
312+
}
313+
const double scale = std::max(std::abs(reference), 1e-12);
314+
if (std::abs(val - reference) > relTol * scale) {
315+
isFlat = false;
316+
break;
317+
}
318+
}
319+
}
320+
321+
obs.setVal(savedVal);
322+
return isFlat;
323+
}
324+
289325
} // namespace RooHelpers
290326

291327
namespace RooFit::Detail {

roofit/roofitcore/src/RooFormulaVar.cxx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
#include "RooAbsRealLValue.h"
5656
#include "RooAbsBinning.h"
5757
#include "RooCurve.h"
58-
#include "RooHelpers.h"
58+
#include "RooFitImplHelpers.h"
5959

6060
#ifdef ROOFIT_LEGACY_EVAL_BACKEND
6161
#include "RooNLLVar.h"
@@ -319,8 +319,14 @@ std::list<double>* RooFormulaVar::binBoundaries(RooAbsRealLValue& obs, double xl
319319
auto found = _binnings.find(_actualVars.index(obs.GetName()));
320320
if (found != _binnings.end()) {
321321
const RooAbsBinning &binning = *found->second;
322-
return RooHelpers::binBoundariesInRange({binning.array(), static_cast<std::size_t>(binning.numBoundaries())}, xlo,
323-
xhi);
322+
auto hint = new std::list<double>;
323+
for (int i = 0; i < binning.numBoundaries(); ++i) {
324+
const double boundary = binning.array()[i];
325+
if (boundary >= xlo && boundary <= xhi) {
326+
hint->push_back(boundary);
327+
}
328+
}
329+
return hint;
324330
}
325331

326332
for (const auto par : _actualVars) {

roofit/roofitcore/src/RooGenericPdf.cxx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ the names of the arguments are not hard coded.
5151
#include "RooAbsRealLValue.h"
5252
#include "RooAbsBinning.h"
5353
#include "RooCurve.h"
54-
#include "RooHelpers.h"
54+
#include "RooFitImplHelpers.h"
5555

5656
using std::istream, std::ostream, std::endl;
5757

@@ -223,8 +223,14 @@ std::list<double> *RooGenericPdf::binBoundaries(RooAbsRealLValue &obs, double xl
223223
return nullptr;
224224
}
225225
const RooAbsBinning &binning = *found->second;
226-
return RooHelpers::binBoundariesInRange({binning.array(), static_cast<std::size_t>(binning.numBoundaries())}, xlo,
227-
xhi);
226+
auto hint = new std::list<double>;
227+
for (int i = 0; i < binning.numBoundaries(); ++i) {
228+
const double boundary = binning.array()[i];
229+
if (boundary >= xlo && boundary <= xhi) {
230+
hint->push_back(boundary);
231+
}
232+
}
233+
return hint;
228234
}
229235

230236
////////////////////////////////////////////////////////////////////////////////

roofit/roofitcore/src/RooHelpers.cxx

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
#include <ROOT/StringUtils.hxx>
3030
#include <TClass.h>
3131

32-
#include <algorithm>
33-
#include <cmath>
3432
#include <unordered_map>
3533

3634
namespace RooHelpers {
@@ -170,60 +168,4 @@ bool setAllConstant(const RooAbsCollection &coll, bool constant)
170168
return changed;
171169
}
172170

173-
bool isFunctionFlatInBins(const RooAbsReal &function, RooAbsRealLValue &obs, std::span<const double> boundaries,
174-
double relTol)
175-
{
176-
// Fractions of the bin width at which the function is sampled. They are kept
177-
// strictly inside the bin (away from the boundaries) so that the evaluation
178-
// is not affected by which side of a boundary a step function jumps.
179-
const double fractions[] = {0.04, 0.27, 0.5, 0.73, 0.96};
180-
181-
const double savedVal = obs.getVal();
182-
183-
bool isFlat = true;
184-
for (std::size_t i = 0; i + 1 < boundaries.size() && isFlat; ++i) {
185-
const double lo = boundaries[i];
186-
const double hi = boundaries[i + 1];
187-
double reference = 0.0;
188-
bool first = true;
189-
for (double frac : fractions) {
190-
obs.setVal(lo + frac * (hi - lo));
191-
const double val = function.getVal();
192-
if (first) {
193-
reference = val;
194-
first = false;
195-
continue;
196-
}
197-
const double scale = std::max(std::abs(reference), 1e-12);
198-
if (std::abs(val - reference) > relTol * scale) {
199-
isFlat = false;
200-
break;
201-
}
202-
}
203-
}
204-
205-
obs.setVal(savedVal);
206-
return isFlat;
207-
}
208-
209-
std::list<double> *binBoundariesInRange(std::span<const double> boundaries, double xlo, double xhi)
210-
{
211-
auto out = new std::list<double>;
212-
213-
// Small tolerance so that boundaries numerically coinciding with the range
214-
// limits are not duplicated by the explicit xlo/xhi endpoints below.
215-
const double delta = (xhi - xlo) * 1e-8;
216-
217-
for (double boundary : boundaries) {
218-
if (boundary > xlo + delta && boundary < xhi - delta) {
219-
out->push_back(boundary);
220-
}
221-
}
222-
223-
out->push_front(xlo);
224-
out->push_back(xhi);
225-
226-
return out;
227-
}
228-
229171
} // namespace RooHelpers

roofit/roofitcore/test/testGenericPdf.cxx

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
// Authors: Stephan Hageboeck, CERN 05/2019
33
// Jonas Rembser, CERN 06/2022
44

5-
#include <RooRealVar.h>
6-
#include <RooGenericPdf.h>
7-
#include <RooProduct.h>
85
#include <RooArgList.h>
96
#include <RooBinning.h>
10-
#include <RooUniformBinning.h>
7+
#include <RooFormulaVar.h>
8+
#include <RooGenericPdf.h>
119
#include <RooHelpers.h>
10+
#include <RooHistPdf.h>
11+
#include <RooProduct.h>
12+
#include <RooRealVar.h>
13+
#include <RooUniformBinning.h>
1214
#include <RooWorkspace.h>
1315

1416
#include <gtest/gtest.h>
@@ -340,3 +342,47 @@ TEST(GenericPdf, BinnedBoundariesSurviveRename)
340342
EXPECT_NE(integratorName.find("RooBinIntegrator"), std::string::npos) << integratorName;
341343
EXPECT_DOUBLE_EQ(value, piecewiseFlatIntegral);
342344
}
345+
346+
// Ensure the implementation of RooGenericPdf::binBoundaries() (and
347+
// RooFormulaVar::binBoundaries()) is consistent with equivalent RooHistPdf.
348+
TEST(GenericPdf, BinnedBoundariesConsistentWithHistPdf)
349+
{
350+
int nBins = 5;
351+
352+
RooRealVar x{"x", "x", 0, 0, 5};
353+
x.setBins(nBins);
354+
355+
RooDataHist dh{"dh", "", x};
356+
357+
RooHistPdf hpdf{"hpdf", "", x, dh};
358+
RooGenericPdf gpdf{"gpdf", "x[0] - x[0] + 1", {x}}; // uniform dummy
359+
gpdf.setBinning(x, x.getBinning());
360+
361+
RooFormulaVar fvar{"fvar", "x[0] - x[0] + 1", {x}}; // uniform dummy
362+
fvar.setBinning(x, x.getBinning());
363+
364+
// intentionally beyond the bin boundaries
365+
double lo = -10;
366+
double hi = 10;
367+
368+
std::unique_ptr<std::list<double>> boundsHistPdf{hpdf.binBoundaries(x, lo, hi)};
369+
std::unique_ptr<std::list<double>> boundsGenericPdf{gpdf.binBoundaries(x, lo, hi)};
370+
std::unique_ptr<std::list<double>> boundsFormulaVar{fvar.binBoundaries(x, lo, hi)};
371+
372+
EXPECT_EQ(boundsHistPdf->size(), nBins + 1);
373+
EXPECT_EQ(boundsGenericPdf->size(), nBins + 1);
374+
EXPECT_EQ(boundsFormulaVar->size(), nBins + 1);
375+
376+
auto iterHistPdf = boundsHistPdf->begin();
377+
auto iterGenericPdf = boundsGenericPdf->begin();
378+
auto iterFormulaVar = boundsFormulaVar->begin();
379+
380+
for (int i = 0; i < nBins + 1; ++i) {
381+
EXPECT_EQ(*iterHistPdf, static_cast<double>(i));
382+
EXPECT_EQ(*iterGenericPdf, static_cast<double>(i));
383+
EXPECT_EQ(*iterFormulaVar, static_cast<double>(i));
384+
iterHistPdf++;
385+
iterGenericPdf++;
386+
iterFormulaVar++;
387+
}
388+
}

0 commit comments

Comments
 (0)