Skip to content

Commit 8ad302b

Browse files
nucleosynthesisNick Wardle
andauthored
Adding option to run expected limits for mu!=0 (#1128)
* Adding option to run expected limits for mu!=0 Added option (`--signalStrengthForExpected`) which can be set to values for which the expected limit and bands should be calculated. * Observed value doesn't change (still assume pb is dervied wrt mu=0) * setting `--signalStrengthForExpected 0` should yield identical results compared to leaving default but might be slower as this forces a recreation of the Asimov dataset * Add some documentation * clang format * Update commonstatsmethods.md with Combine info Added note about non-support for non-zero signal strength expected limits based on toys --------- Co-authored-by: Nick Wardle <nckw@cern.ch>
1 parent 145d757 commit 8ad302b

3 files changed

Lines changed: 57 additions & 11 deletions

File tree

docs/part3/commonstatsmethods.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ hadd limits.root higgsCombine*.AsymptoticLimits.*
9090

9191
combine -M AsymptoticLimits realistic-counting-experiment.txt --getLimitFromGrid limits.root
9292
```
93+
## Expected limits assuming non-zero signal strengths
94+
95+
The median expected limit and quantiles can be calculated under Hypotheses other than at $\mu=0$. The Asymptotic expected limits can be calculated by setting the value `--signalStrengthForExpected mu0`. The Asimov dataset produced for the calculation will be generated for $\mu=$`mu0` instead of the usual $\mu=0$. Note that setting ` --signalStrengthForExpected 0` will give the same results as not setting the option, but will be slower as this forces a recreation of the Asimov dataset.
96+
97+
Be aware that while expected values are derived from the Asimov created at the specified signal strength, setting this option *does not* alter the definition of $CL_s$ in that the denominator is still defined with respect to the no signal ($\mu=0$) hypothesis.
98+
99+
!!! info
100+
Currently <span style="font-variant:small-caps;">Combine</span> does *not* support calculating expected limits assuming non-zero signal strengths using toy-based calculations. If you are interested in adding this feature, please contact the developers.
93101

94102
## Asymptotic Significances
95103

interface/AsymptoticLimits.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class AsymptoticLimits : public LimitAlgo {
4848
//static int minimizerStrategy_;
4949

5050
static double rValue_;
51+
static double signalStrengthForExpected_;
5152

5253
static bool strictBounds_;
5354

@@ -66,7 +67,7 @@ class AsymptoticLimits : public LimitAlgo {
6667

6768
float calculateLimitFromGrid(RooRealVar *, double, double);
6869

69-
RooAbsData *asimovDataset(RooWorkspace *w, RooStats::ModelConfig *mc_s, RooStats::ModelConfig *mc_b, RooAbsData &data);
70+
RooAbsData *asimovDataset(RooWorkspace *w, RooStats::ModelConfig *mc_s, RooStats::ModelConfig *mc_b, RooAbsData &data, bool overwrite=false);
7071
double getCLs(RooRealVar &r, double rVal, bool getAlsoExpected=false, double *limit=0, double *limitErr=0);
7172

7273
TFile *gridFile_;
@@ -75,6 +76,8 @@ class AsymptoticLimits : public LimitAlgo {
7576
double readMU_;
7677
bool doCLs_;
7778

79+
bool doNonStandardAsimov_;
80+
7881
};
7982

8083
#endif

src/AsymptoticLimits.cc

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ std::string AsymptoticLimits::minosAlgo_ = "stepping";
3737
//float AsymptoticLimits::minimizerTolerance_ = 0.01;
3838
//int AsymptoticLimits::minimizerStrategy_ = 0;
3939
double AsymptoticLimits::rValue_ = 1.0;
40+
double AsymptoticLimits::signalStrengthForExpected_ = 0.0;
4041
bool AsymptoticLimits::strictBounds_ = false;
4142

4243
RooAbsData * AsymptoticLimits::asimovDataset_ = nullptr;
@@ -46,6 +47,7 @@ LimitAlgo("AsymptoticLimits specific options") {
4647
options_.add_options()
4748
("rAbsAcc", boost::program_options::value<double>(&rAbsAccuracy_)->default_value(rAbsAccuracy_), "Absolute accuracy on r to reach to terminate the scan")
4849
("rRelAcc", boost::program_options::value<double>(&rRelAccuracy_)->default_value(rRelAccuracy_), "Relative accuracy on r to reach to terminate the scan")
50+
("signalStrengthForExpected", boost::program_options::value<double>(&signalStrengthForExpected_)->default_value(signalStrengthForExpected_), "Signal strength for expected limits (0=background only, default is 0)")
4951
("run", boost::program_options::value<std::string>(&what_)->default_value(what_), "What to run: both (default), observed, expected, blind.")
5052
("singlePoint", boost::program_options::value<double>(&rValue_), "Just compute CLs for the given value of r")
5153
//("minimizerAlgo", boost::program_options::value<std::string>(&minimizerAlgo_)->default_value(minimizerAlgo_), "Choice of minimizer used for profiling (Minuit vs Minuit2)")
@@ -89,7 +91,8 @@ void AsymptoticLimits::applyOptions(const boost::program_options::variables_map
8991
limitsTree_->SetBranchAddress("limit",&readCL_);
9092
limitsTree_->SetBranchAddress("r",&readMU_);
9193
}
92-
94+
95+
doNonStandardAsimov_ = vm.count("signalStrengthForExpected") && !vm["signalStrengthForExpected"].defaulted();
9396
}
9497

9598
void AsymptoticLimits::applyDefaultOptions() {
@@ -151,7 +154,12 @@ bool AsymptoticLimits::runLimit(RooWorkspace *w, RooStats::ModelConfig *mc_s, Ro
151154
}
152155

153156
w->loadSnapshot("clean");
154-
RooAbsData &asimov = *asimovDataset(w, mc_s, mc_b, data);
157+
158+
// Bit of a waste of time if we are not using a non-standard value
159+
double tmpsexp = signalStrengthForExpected_;
160+
signalStrengthForExpected_ = 0.0;
161+
RooAbsData &asimov = *asimovDataset(w, mc_s, mc_b, data, /*overwrite=*/doNonStandardAsimov_);
162+
signalStrengthForExpected_ = tmpsexp;
155163
w->loadSnapshot("clean");
156164

157165
r->setConstant(false);
@@ -381,7 +389,7 @@ std::vector<std::pair<float,float> > AsymptoticLimits::runLimitExpected(RooWorks
381389
}
382390

383391
// 2) get asimov dataset
384-
RooAbsData *asimov = asimovDataset(w, mc_s, mc_b, data);
392+
RooAbsData *asimov = asimovDataset(w, mc_s, mc_b, data, /*overwrite=*/ doNonStandardAsimov_);
385393

386394
// 2b) load asimov global observables
387395
if (params_.get() == 0) params_.reset(mc_s->GetPdf()->getParameters(data));
@@ -408,7 +416,7 @@ std::vector<std::pair<float,float> > AsymptoticLimits::runLimitExpected(RooWorks
408416
std::unique_ptr<RooFitResult> res(minim.save());
409417
res->Print("V");
410418
}
411-
if (r->getVal()/r->getMax() > 1e-3) {
419+
if (r->getVal()/r->getMax() > 1e-3 && !doNonStandardAsimov_) {
412420
if (verbose) {
413421
CombineLogger::instance().log("AsymptoticLimits.cc",__LINE__,std::string(Form("[WARNING] Best fit of asimov dataset is at %s = %f (%f times %sMax), while it should be at zero",r->GetName(), r->getVal(), r->getVal()/r->getMax(), r->GetName())),__func__);
414422
}
@@ -463,8 +471,33 @@ float AsymptoticLimits::findExpectedLimitFromCrossing(RooAbsReal &nll, RooRealVa
463471
// crossing value of mu that gives the specified qmu in the above.
464472
// note that as in CCGV the asymptotic formula for upper limits in qmu and qmutilde are identical so can use qmu here.
465473

474+
// only need to modify the value of pb compared to the typical case where mu'=0 for the asimov dataset
475+
double pb_expected = pb;
466476
double N = ROOT::Math::normal_quantile(pb, 1.0);
467-
double errorlevel = 0.5 * pow(N+ROOT::Math::normal_quantile_c((doCLs_ ? pb:1.)*(1-cl),1.0), 2);
477+
478+
// Things get tricker here so first, we use the Asimov value of the test stat and plug it into
479+
if (doNonStandardAsimov_) {
480+
481+
// std::cout << "Using non-standard asimov dataset with signal strength " << signalStrengthForExpected_ << std::endl;
482+
// Need to find q(0)
483+
r->setVal(0); r->setConstant(true);
484+
CascadeMinimizer minim2(nll, CascadeMinimizer::Constrained);
485+
if (hasDiscreteParams_) minim2.minimize(verbose-2);
486+
else minim2.improve(verbose-2);
487+
double q_At_0 = 2*nll.getVal();
488+
r->setVal(signalStrengthForExpected_);
489+
if (hasDiscreteParams_) minim2.minimize(verbose-2);
490+
else minim2.improve(verbose-2);
491+
double q_At_muA = 2*nll.getVal();
492+
r->setConstant(false);
493+
494+
double qMuAsimov = q_At_0-q_At_muA;
495+
double N_for_expected = N+ROOT::Math::sqrt(qMuAsimov);
496+
pb_expected = ROOT::Math::normal_cdf(N_for_expected, 1.0);
497+
// std::cout << " --> this gives pb = " << pb_expected << " (N=" << N_for_expected << ")" << std::endl;
498+
}
499+
500+
double errorlevel = 0.5 * pow(N+ROOT::Math::normal_quantile_c((doCLs_ ? pb_expected:1.)*(1-cl),1.0), 2);
468501
int minosStat = -1;
469502
if (minosAlgo_ == "minos") {
470503
double rMax0 = r->getMax();
@@ -730,13 +763,15 @@ float AsymptoticLimits::calculateLimitFromGrid(RooRealVar *r , double quantile,
730763
return rlim;
731764
}
732765

733-
RooAbsData * AsymptoticLimits::asimovDataset(RooWorkspace *w, RooStats::ModelConfig *mc_s, RooStats::ModelConfig *mc_b, RooAbsData &data) {
766+
RooAbsData * AsymptoticLimits::asimovDataset(RooWorkspace *w, RooStats::ModelConfig *mc_s, RooStats::ModelConfig *mc_b, RooAbsData &data, bool overwrite) {
734767
// Do this only once
735768
// if (w->data("_Asymptotic_asimovDataset_") != 0) {
736769
// return w->data("_Asymptotic_asimovDataset_");
737770
// }
738771

739-
if (asimovDataset_) {
772+
773+
if (asimovDataset_ && !overwrite) {
774+
//std::cerr << "Reusing asimov dataset" << std::endl;
740775
return asimovDataset_;
741776
}
742777
// snapshot data global observables
@@ -748,9 +783,9 @@ RooAbsData * AsymptoticLimits::asimovDataset(RooWorkspace *w, RooStats::ModelCon
748783
gobs.snapshot(snapGlobalObsData);
749784
}
750785
// get asimov dataset and global observables
751-
asimovDataset_ = (noFitAsimov_ ? asimovutils::asimovDatasetNominal(mc_s, 0.0, verbose) :
752-
asimovutils::asimovDatasetWithFit(mc_s, data, snapGlobalObsAsimov,!bypassFrequentistFit_, 0.0, verbose));
753-
asimovDataset_->SetName("_Asymptotic_asimovDataset_");
786+
asimovDataset_ = (noFitAsimov_ ? asimovutils::asimovDatasetNominal(mc_s, signalStrengthForExpected_, verbose) :
787+
asimovutils::asimovDatasetWithFit(mc_s, data, snapGlobalObsAsimov,!bypassFrequentistFit_, signalStrengthForExpected_, verbose));
788+
asimovDataset_->SetName(Form("_Asymptotic_asimovDataset_%d_%g",doNonStandardAsimov_,signalStrengthForExpected_)); // in case we want to keep multiple asimov datasets in the same workspace
754789
// w->import(*asimovData); // I'm assuming the Workspace takes ownership. Might be false.
755790
// delete asimovData; // ^^^^^^^^----- now assuming that the workspace clones.
756791
return asimovDataset_;

0 commit comments

Comments
 (0)