Skip to content

Conversation

@nucleosynthesis
Copy link
Contributor

@nucleosynthesis nucleosynthesis commented Oct 13, 2025

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

Summary by CodeRabbit

  • New Features

    • Added a command-line option to set a non-zero signal strength for expected-limit calculations.
    • Supports generating expected limits using Asimov datasets built at the configured signal strength (non-standard Asimov).
  • Refactor

    • Unified handling of standard vs non-standard Asimov workflows across expected-limit and CLs computations.
    • Optional reuse or forced regeneration of Asimov datasets to avoid unnecessary recomputation.
  • Documentation

    • New section explaining expected limits with non-zero signal strengths and guidance for the new option.

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
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 13, 2025

Walkthrough

Adds a configurable non-standard Asimov mode via a new static signal-strength parameter and internal flag, extends the Asimov dataset API with an overwrite toggle, threads non-standard Asimov handling through expected-limit control flow, and registers a CLI option to set the signal strength.

Changes

Cohort / File(s) Summary
Interface updates
interface/AsymptoticLimits.h
- Added static double signalStrengthForExpected_; (public)
- Added bool doNonStandardAsimov_; (private)
- Changed signature: asimovDataset(..., RooAbsData &data)asimovDataset(..., RooAbsData &data, bool overwrite=false)
Implementation and control flow
src/AsymptoticLimits.cc
- Added CLI option --signalStrengthForExpected and initialization of signalStrengthForExpected_ (default 0.0)
- Set doNonStandardAsimov_ when non-default signal strength provided
- Propagated overwrite flag to asimovDataset calls and named Asimov datasets to include mode/value for caching
- Temporarily zeroes/restores signalStrengthForExpected_ for standard Asimov construction where required
- Added branches computing expected pb using non-standard Asimov q(mu) values (affecting runLimit, runLimitExpected, findExpectedLimitFromCrossing, and CLs paths)
Documentation
docs/part3/commonstatsmethods.md
- Added "Expected limits assuming non-zero signal strengths" section describing --signalStrengthForExpected mu0, Asimov generation at mu0, cache/performance note, and CLs denominator clarification

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CLI as CLI
  participant AL as AsymptoticLimits
  participant WS as RooWorkspace
  participant MCS as ModelConfig(s)
  participant MCB as ModelConfig(b)

  CLI->>AL: parse Options (--signalStrengthForExpected = muA)
  AL->>AL: signalStrengthForExpected_ = muA
  AL->>AL: doNonStandardAsimov_ = (muA != 0.0)

  rect rgba(220,235,255,0.25)
    note right of AL: Asimov dataset creation / reuse
    AL->>AL: asimovDataset(WS, MCS, MCB, data, overwrite)
    alt overwrite==false and cached
      AL-->>CLI: return cached Asimov
    else
      AL->>AL: decide standard vs non-standard (doNonStandardAsimov_)
      AL->>WS: build Asimov with mu = signalStrengthForExpected_
      AL-->>CLI: return new Asimov (name includes mode & mu)
    end
  end

  rect rgba(235,255,220,0.25)
    note right of AL: Expected-limit computation flow
    AL->>AL: runLimitExpected()
    alt doNonStandardAsimov_ == true
      AL->>AL: compute q(mu) using non-standard Asimov (q_At_muA, q_At_0)
      AL->>AL: derive pb_expected from non-standard q values
    else
      AL->>AL: compute expected limit with standard Asimov (mu=0)
    end
    AL-->>CLI: return expected CLs/limit
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I twitch my ears at options new,
A mu to guide what datasets do.
Cache a standard, or craft a bold Asimov,
I hop between zeros and a signal to love.
Quiet paws, I nudge CLs toward the light. 🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the primary change of the pull request by indicating the addition of an option to compute expected limits for mu values not equal to zero, which directly reflects the new --signalStrengthForExpected feature and its intent without including unnecessary detail.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nckw_attempt_adding_expected_limits_for_nonzero_bkg

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

//static int minimizerStrategy_;

static double rValue_;
static double signalStrengthForExpected_;
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: invalid case style for variable 'signalStrengthForExpected_' [readability-identifier-naming]

Suggested change
static double signalStrengthForExpected_;
static double signalStrengthForExpected;

double readMU_;
bool doCLs_;

bool doNonStandardAsimov_;
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: invalid case style for private member 'doNonStandardAsimov_' [readability-identifier-naming]

Suggested change
bool doNonStandardAsimov_;
bool m_doNonStandardAsimov;

}

if (vm.count("signalStrengthForExpected") && !vm["signalStrengthForExpected"].defaulted()) {
doNonStandardAsimov_ = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: redundant boolean literal in conditional assignment [readability-simplify-boolean-expr]

src/AsymptoticLimits.cc:94:

-     if (vm.count("signalStrengthForExpected") && !vm["signalStrengthForExpected"].defaulted()) {
-         doNonStandardAsimov_ = true;
-     } else doNonStandardAsimov_ = false;
+     doNonStandardAsimov_ = vm.count("signalStrengthForExpected") && !vm["signalStrengthForExpected"].defaulted();

r->setConstant(false);

double qMuAsimov = q_At_0-q_At_muA;
double N_for_expected = N+ROOT::Math::sqrt(qMuAsimov);
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: no header providing "ROOT::Math::sqrt" is directly included [misc-include-cleaner]

src/AsymptoticLimits.cc:3:

- #include <RooRealVar.h>
+ #include <Math/Math.h>
+ #include <RooRealVar.h>

// std::cout << " --> this gives pb = " << pb_expected << " (N=" << N_for_expected << ")" << std::endl;
}

double errorlevel = 0.5 * pow(N+ROOT::Math::normal_quantile_c((doCLs_ ? pb_expected:1.)*(1-cl),1.0), 2);
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: no header providing "ROOT::Math::normal_quantile_c" is directly included [misc-include-cleaner]

    double errorlevel = 0.5 * pow(N+ROOT::Math::normal_quantile_c((doCLs_ ? pb_expected:1.)*(1-cl),1.0), 2);
                                                ^

asimovDataset_->SetName("_Asymptotic_asimovDataset_");
asimovDataset_ = (noFitAsimov_ ? asimovutils::asimovDatasetNominal(mc_s, signalStrengthForExpected_, verbose) :
asimovutils::asimovDatasetWithFit(mc_s, data, snapGlobalObsAsimov,!bypassFrequentistFit_, signalStrengthForExpected_, verbose));
asimovDataset_->SetName(Form("_Asymptotic_asimovDataset_%d_%g",doNonStandardAsimov_,signalStrengthForExpected_)); // in case we want to keep multiple asimov datasets in the same workspace
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: do not call c-style vararg functions [cppcoreguidelines-pro-type-vararg]

    asimovDataset_->SetName(Form("_Asymptotic_asimovDataset_%d_%g",doNonStandardAsimov_,signalStrengthForExpected_)); // in case we want to keep multiple asimov datasets in the same workspace
                            ^

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/AsymptoticLimits.cc (1)

160-165: Consider using RAII for exception safety.

The temporary modification of signalStrengthForExpected_ could leave it in an incorrect state if an exception is thrown between setting and restoring. Consider using a scope guard or RAII wrapper.

Example pattern:

struct SignalStrengthGuard {
    double& ref;
    double saved;
    SignalStrengthGuard(double& r, double newVal) : ref(r), saved(r) { r = newVal; }
    ~SignalStrengthGuard() { ref = saved; }
};

SignalStrengthGuard guard(signalStrengthForExpected_, 0.0);
RooAbsData &asimov = *asimovDataset(w, mc_s, mc_b, data, /*overwrite=*/doNonStandardAsimov_);
// guard automatically restores on scope exit
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2b7972 and 0765fc4.

📒 Files selected for processing (2)
  • interface/AsymptoticLimits.h (3 hunks)
  • src/AsymptoticLimits.cc (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
interface/AsymptoticLimits.h (1)
src/AsymptoticLimits.cc (2)
  • asimovDataset (769-795)
  • asimovDataset (769-769)
src/AsymptoticLimits.cc (1)
src/AsimovUtils.cc (4)
  • asimovDatasetNominal (17-36)
  • asimovDatasetNominal (17-17)
  • asimovDatasetWithFit (38-177)
  • asimovDatasetWithFit (38-38)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: review
  • GitHub Check: dev3/latest - ROOT LCG master
  • GitHub Check: CMSSW_14_2_0_pre2_ROOT632 - ROOT 6.32.06
  • GitHub Check: CMSSW_14_1_0_pre4 - ROOT 6.30.07
  • GitHub Check: LCG_108 - ROOT 6.36.02
  • GitHub Check: CMSSW_11_3_4 - ROOT 6.22.09
  • GitHub Check: CMSSW_14_0_0_pre1 - ROOT 6.26.11
  • GitHub Check: Compile (py3.10, root6.32.2)
  • GitHub Check: Compile (py3.12, root6.34.4)
  • GitHub Check: Compile (py3.10, root6.26.4)
  • GitHub Check: precheckin
🔇 Additional comments (11)
interface/AsymptoticLimits.h (3)

51-51: LGTM!

The new static member for signal strength configuration is well-placed and follows the existing pattern for static configuration members.


70-70: LGTM!

The overwrite parameter addition is backward compatible with its default value of false, maintaining existing behavior when not explicitly specified.


79-80: LGTM!

The new private flag to track non-standard Asimov mode is appropriately scoped as a private member.

src/AsymptoticLimits.cc (8)

40-40: LGTM!

Static member initialization follows the existing pattern and correctly defaults to 0.0 (background-only).


50-50: LGTM!

The CLI option is properly configured with a clear description that matches the PR objectives.


94-97: LGTM!

The logic correctly identifies when non-standard Asimov mode is active based on whether the option was explicitly provided (even if set to 0), aligning with the PR description that setting it to 0 explicitly should force recreation.


395-395: LGTM!

Correctly passes the overwrite flag to ensure the Asimov dataset is recreated with the appropriate signal strength for expected limits.


422-422: LGTM!

The conditional correctly skips the warning when using non-standard Asimov, since a non-zero best fit is expected in that scenario.


503-503: LGTM!

Correctly uses pb_expected (computed from non-standard Asimov) instead of pb in the error level calculation.


769-795: LGTM! Overwrite and reuse logic is sound.

The implementation correctly:

  • Reuses existing dataset when !overwrite (line 776-778)
  • Passes signalStrengthForExpected_ to both Asimov creation methods (lines 789-790)
  • Creates unique dataset names including the flag and signal strength value (line 791)

This ensures that when using non-standard mode, each call (observed vs expected) creates its own Asimov with the correct μ value without unintended reuse.


477-501: Asymptotic non-standard Asimov formulas are correctly implemented.
qMuAsimov = q(0Asimov)−q(μAAsimov) gives the non-centrality λ, and
N_for_expected = N+√λ with pb_expected = Φ(N_for_expected) matches CCGV Eq.17 and CMS-NOTE 2011-005 Eq.37.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/AsymptoticLimits.cc (1)

474-498: Address missing include for ROOT::Math functions.

The past review identified that no header providing ROOT::Math::sqrt is directly included (line 495). Similarly, ROOT::Math::normal_quantile_c (line 500) lacks a direct include. As per the past review comments, please add the missing include.

Apply this diff to add the missing header:

 #include <RooRealVar.h>
+#include <Math/Math.h>
 #include <RooArgSet.h>

Based on past review comments.

🧹 Nitpick comments (2)
src/AsymptoticLimits.cc (2)

157-162: Consider refactoring to avoid temporary static member modification.

The pattern of temporarily modifying signalStrengthForExpected_ is not thread-safe and introduces confusing side effects on static state. Consider refactoring to pass the signal strength value directly as a parameter to asimovDataset instead of relying on the static member, or ensure this code path is never executed concurrently.


788-788: Note: c-style vararg function usage.

The past review flagged the use of Form() as a c-style vararg function. While this is a minor code quality issue, you may want to consider using safer alternatives like std::string formatting in the future.

Based on past review comments.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0765fc4 and e7d161b.

📒 Files selected for processing (2)
  • docs/part3/commonstatsmethods.md (1 hunks)
  • src/AsymptoticLimits.cc (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/AsymptoticLimits.cc (1)
src/AsimovUtils.cc (4)
  • asimovDatasetNominal (17-36)
  • asimovDatasetNominal (17-17)
  • asimovDatasetWithFit (38-177)
  • asimovDatasetWithFit (38-38)
🪛 markdownlint-cli2 (0.18.1)
docs/part3/commonstatsmethods.md

95-95: Spaces inside code span elements

(MD038, no-space-in-code)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: review
  • GitHub Check: precheckin
  • GitHub Check: Compile (py3.10, root6.26.4)
  • GitHub Check: Compile (py3.12, root6.34.4)
  • GitHub Check: Compile (py3.10, root6.32.2)
  • GitHub Check: dev3/latest - ROOT LCG master
  • GitHub Check: CMSSW_11_3_4 - ROOT 6.22.09
  • GitHub Check: LCG_108 - ROOT 6.36.02
  • GitHub Check: CMSSW_14_2_0_pre2_ROOT632 - ROOT 6.32.06
  • GitHub Check: CMSSW_14_1_0_pre4 - ROOT 6.30.07
  • GitHub Check: CMSSW_14_0_0_pre1 - ROOT 6.26.11
🔇 Additional comments (3)
src/AsymptoticLimits.cc (2)

40-40: LGTM!

The static member initialization is consistent with the CLI option default value.


766-776: LGTM!

The overwrite parameter correctly controls whether to reuse the cached Asimov dataset. The logic ensures that when overwrite is false and a cached dataset exists, it's returned immediately, avoiding unnecessary recomputation.

docs/part3/commonstatsmethods.md (1)

93-97: Documentation clearly explains the new feature.

The documentation accurately describes the behavior of --signalStrengthForExpected and correctly notes that while setting it to 0 produces the same results as the default, it will be slower due to forcing Asimov dataset recreation. The clarification about the CLs denominator remaining defined with respect to μ=0 is also helpful.

```
## Expected limits assuming non-zero signal strengths

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix markdown formatting: remove extra space in code span.

There's an extra space before the -- in the code span. This triggers a markdown linter warning.

Apply this diff:

-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.
+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.

As per static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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.
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.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

95-95: Spaces inside code span elements

(MD038, no-space-in-code)

🤖 Prompt for AI Agents
In docs/part3/commonstatsmethods.md around line 95, there's an extra space
inside the inline code span before the option (` ` --signalStrengthForExpected
0` ), which triggers a markdown linter warning; remove the leading space so the
span reads `--signalStrengthForExpected 0` (update the single inline code
occurrence at that line to eliminate the extra space).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/AsymptoticLimits.cc (1)

94-96: Boolean expression can be simplified.

As noted in a previous review, the conditional assignment can be simplified to a direct boolean expression.

🧹 Nitpick comments (1)
src/AsymptoticLimits.cc (1)

157-162: Consider optimizing the save/restore pattern.

The code saves and restores signalStrengthForExpected_ even when it's unnecessary (when doNonStandardAsimov_ is false and the value is already 0.0). While the current implementation is correct, you could optimize by conditionally performing the save/restore:

-  // Bit of a waste of time if we are not using a non-standard value 
-  double tmpsexp = signalStrengthForExpected_;
-  signalStrengthForExpected_ = 0.0;
+  double tmpsexp = 0.0;
+  if (doNonStandardAsimov_) {
+    tmpsexp = signalStrengthForExpected_;
+    signalStrengthForExpected_ = 0.0;
+  }
   RooAbsData &asimov = *asimovDataset(w, mc_s, mc_b, data, /*overwrite=*/doNonStandardAsimov_);
-  signalStrengthForExpected_ = tmpsexp;
+  if (doNonStandardAsimov_) {
+    signalStrengthForExpected_ = tmpsexp;
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7d161b and a8e70c8.

📒 Files selected for processing (1)
  • src/AsymptoticLimits.cc (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/AsymptoticLimits.cc (1)
src/AsimovUtils.cc (4)
  • asimovDatasetNominal (17-36)
  • asimovDatasetNominal (17-17)
  • asimovDatasetWithFit (38-177)
  • asimovDatasetWithFit (38-38)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: CMSSW_14_1_0_pre4 - ROOT 6.30.07
  • GitHub Check: dev3/latest - ROOT LCG master
  • GitHub Check: CMSSW_14_0_0_pre1 - ROOT 6.26.11
  • GitHub Check: LCG_108 - ROOT 6.36.02
  • GitHub Check: CMSSW_14_2_0_pre2_ROOT632 - ROOT 6.32.06
  • GitHub Check: CMSSW_11_3_4 - ROOT 6.22.09
  • GitHub Check: Compile (py3.10, root6.26.4)
  • GitHub Check: Compile (py3.12, root6.34.4)
  • GitHub Check: Compile (py3.10, root6.32.2)
  • GitHub Check: precheckin
  • GitHub Check: review
🔇 Additional comments (6)
src/AsymptoticLimits.cc (6)

40-40: LGTM! Proper initialization of the new parameter.

The static member is correctly initialized to 0.0, maintaining backward compatibility with the default background-only (mu=0) behavior.


50-50: LGTM! Clear and well-documented command-line option.

The option is properly registered with an accurate description that explains the default behavior.


392-392: LGTM! Correct overwrite logic for expected limits.

Passing doNonStandardAsimov_ as the overwrite parameter ensures the cached dataset is recreated when using a non-zero signal strength, preventing reuse of an incompatible cached dataset.


419-423: LGTM! Appropriate warning suppression.

Correctly suppresses the warning when using non-standard Asimov datasets, since a non-zero best fit is expected and intentional in that case.


474-498: LGTM! Correct implementation of non-standard Asimov expected limits.

The logic correctly computes the modified pb_expected value by:

  1. Evaluating the test statistic at mu=0 and mu=muA via constrained minimization
  2. Computing the difference to get the Asimov test statistic
  3. Adjusting the expected p-value using the asymptotic formula

The discrete-parameter-aware minimization (line 485-486, 489-490) is appropriately handled.

Note: Line 495 uses ROOT::Math::sqrt, which has been flagged in a previous review for a missing header include.


766-792: LGTM! Correct caching logic for Asimov datasets.

The changes properly handle caching and recreation of Asimov datasets:

  • The overwrite parameter controls whether to reuse the cached dataset
  • The signalStrengthForExpected_ value is correctly passed to both generation functions
  • The dataset name (line 788) includes both the flag and value, helping distinguish different parameter combinations

Notes:

  • Line 788's use of Form() has been flagged in previous reviews for c-style vararg usage
  • Line 773's caching strategy is correct but recreates datasets more often than strictly necessary when doNonStandardAsimov_=true (both the mu=0 and mu!=0 datasets are recreated each run, even though the mu=0 dataset could be reused). This is a minor inefficiency acceptable for the current implementation.

@nucleosynthesis
Copy link
Contributor Author

nucleosynthesis commented Nov 10, 2025

I've been holding off on merging this as ideally we would have the same functionality to calculate these limits with toys. However, looking through HybridNew, it seems quite difficult as we need to carry around an additional sampling distribution for the test statistic evaluated on toys generated assuming some $0 &lt; \mu^{\prime} &lt; \mu$.

The simplest would be to modify the HypoTestResult with a third sampling distribution that can be called if needed - i.e if calculating the expected upper limit assuming something other than mu=0. Note this is not the same as simply setting the "background-only" toys to be mu=1 or similar since then the definition of CLs would change.

We can merge this for now and possibly in the future, develop the HybridNew method to allow doing the same with toys.

@nucleosynthesis
Copy link
Contributor Author

@kcormi , perhaps something for a keen summer/PhD student to develop & validate?

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.

2 participants