Skip to content

multi poi support - #69

Open
MoAly98 wants to merge 17 commits into
mainfrom
maly/multi-poi-support
Open

multi poi support#69
MoAly98 wants to merge 17 commits into
mainfrom
maly/multi-poi-support

Conversation

@MoAly98

@MoAly98 MoAly98 commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added multi-parameter hypothesis testing, including joint p-values and confidence regions.
    • Added toy-based calculators and configurable limit solvers for root finding, grid scans, and bisection.
    • Added support for mapping-based POI inputs and flexible Asimov dataset configuration.
    • Added confidence-region result reporting and expanded statistical distribution support.
  • Documentation

    • Updated quickstart, API, extension, and tips documentation for the new hypothesis-testing workflows.
    • Added a comprehensive multi-POI user guide.
    • Updated terminology and examples for p-value bands and limit solvers.
  • Tests

    • Expanded coverage for multi-POI tests, toy calculations, confidence regions, and limit solvers.

MoAly98 added 17 commits July 9, 2026 15:45
…lculator owns the fixed distribution; ToyCalculator requires its generator and key
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR refactors hypothesis testing around mapping-based POI inputs, adds ToyCalculator, confidence regions, and a LimitSolver framework. It renames expected p-value APIs, updates asymptotic distributions, removes upper-limit helper exports, and revises documentation and tests for multi-POI workflows.

Changes

Hypothesis-testing API overhaul

Layer / File(s) Summary
POI contracts and result shapes
src/everwillow/_src/inference/hypotest/{test_statistics,toys,utils,results}.py
POIs, Asimov settings, constrained fits, toy hypotheses, and result payloads now use mappings; confidence-region results are added.
Calculator orchestration and stochastic evaluation
src/everwillow/_src/inference/hypotest/calculators.py
Calculators centralize p-value bands, limits, POI resolution, confidence regions, and keyed toy evaluation; ToyCalculator is added.
Distribution and p-value-band updates
src/everwillow/_src/inference/hypotest/distributions.py
expected_pvalues becomes pvalue_bands; TMuAsymptotic supports configurable degrees of freedom and non-central chi-square tails.
Limit solver framework
src/everwillow/_src/inference/hypotest/limit_solvers.py
Root-finding, grid-scan, and stochastic bisection solvers implement a common objective contract.
Public exports and documentation
src/everwillow/hypotest/*, docs/*
New calculators, solvers, multi-POI guidance, Asimov terminology, and calculator-based limit examples are exposed and documented.
Behavioral validation
tests/inference/hypotest/*
Tests cover multi-POI statistics, calculator seams, toy reproducibility, p-value bands, confidence regions, utilities, and limit solvers.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

  • MoAly98/everwillow#35 — Introduced the earlier upper-limit functionality that this PR replaces with LimitSolver.
  • MoAly98/everwillow#45 — Shares the hypothesis-testing API migration around compute() and mapped toy hypotheses.
  • MoAly98/everwillow#58 — Shares calculator Asimov-observation forwarding and constrained-fit POI handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding multi-POI support across the hypothesis-testing API and docs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch maly/multi-poi-support
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch maly/multi-poi-support

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/extending-hypotest.md (1)

159-182: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Import jax.numpy in the custom sampling example.

The changed code calls jnp.sqrt and jnp.maximum, but imports only jax, so copying the example raises NameError.

Proposed fix
 import jax
+import jax.numpy as jnp
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/extending-hypotest.md` around lines 159 - 182, Update the custom
sampling example imports to include jax.numpy under the jnp alias used by
sample_fn, so jnp.sqrt and jnp.maximum resolve when the example is copied.
docs/tips.md (1)

12-26: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Import AsymptoticCalculator in the JIT example.

The updated snippet constructs AsymptoticCalculator but never imports it, so it cannot run as written.

Proposed fix
 import jax
+from everwillow.hypotest.calculators import AsymptoticCalculator
 from everwillow.hypotest.limit_solvers import RootFindingLimitSolver
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/tips.md` around lines 12 - 26, Add the missing AsymptoticCalculator
import to the JIT example so the compute_limit function can construct it
successfully, while preserving the existing RootFindingLimitSolver import and
calculation flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/extending-hypotest.md`:
- Around line 205-214: Update the ToyGenerator examples to define a minimal
predict function before the first use of predict, or clearly state that it must
come from earlier user code. Ensure both map_fn variants continue using the same
defined prediction function.

In `@docs/multi-poi.md`:
- Around line 166-173: Update the “Toy-based regions” example to define toy_calc
before calling confidence_region, either by adding a concise ToyCalculator
construction with the required import or by explicitly stating that toy_calc is
an already-configured instance. Ensure the example is self-contained enough to
avoid an undefined-name error.
- Around line 83-84: Add the missing imports for BisectionLimitSolver and
Matplotlib’s pyplot as plt before the examples in docs/multi-poi.md, ensuring
both references in the upper_limit examples are defined.

In `@docs/quickstart.md`:
- Around line 231-232: Update the quickstart documentation around the
toy-hypothesis descriptions to distinguish null toys, which use the tested POI
of 1.0, from alternative toys, which use poi_alt (0.0 here). Apply the same
correction to the corresponding explanation near the additionally referenced
section, while preserving the existing tail-counting and distribution_factory
guidance.

In `@src/everwillow/_src/inference/hypotest/calculators.py`:
- Around line 10-12: Update the module docstring entry for AsymptoticCalculator
to replace the stale mu_asimov field name with poi_asimov, while preserving the
existing predict_fn and asimov_observation references and description.

In `@src/everwillow/_src/inference/hypotest/limit_solvers.py`:
- Around line 283-291: Update the bisect iteration logic around the converged
flag so lo and hi remain unchanged when the evaluated mid satisfies the
tolerance; only narrow the bracket when not converged. Preserve the existing
directional updates for non-converged iterations, ensuring the post-loop
final_mid equals the converged mid.

In `@src/everwillow/_src/inference/hypotest/results.py`:
- Line 132: Update the docstring Attributes entry in the relevant results class
to use the field name distribution instead of the stale dist name, keeping the
description unchanged.

In `@src/everwillow/hypotest/limit_solvers.py`:
- Around line 20-26: Reorder the entries in the __all__ declaration
alphabetically to satisfy Ruff RUF022: place BisectionLimitSolver,
GridScanLimitSolver, LimitSolver, RootFindingLimitSolver, and
StochasticLimitSolver in that order.

---

Outside diff comments:
In `@docs/extending-hypotest.md`:
- Around line 159-182: Update the custom sampling example imports to include
jax.numpy under the jnp alias used by sample_fn, so jnp.sqrt and jnp.maximum
resolve when the example is copied.

In `@docs/tips.md`:
- Around line 12-26: Add the missing AsymptoticCalculator import to the JIT
example so the compute_limit function can construct it successfully, while
preserving the existing RootFindingLimitSolver import and calculation flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e832f783-de5c-4557-827d-bba471830251

📥 Commits

Reviewing files that changed from the base of the PR and between 528dd62 and eaef5f3.

📒 Files selected for processing (29)
  • docs/api/inference/hypotest.md
  • docs/asimov-datasets.md
  • docs/extending-hypotest.md
  • docs/index.md
  • docs/multi-poi.md
  • docs/quickstart.md
  • docs/tips.md
  • pyproject.toml
  • src/everwillow/_src/inference/hypotest/calculators.py
  • src/everwillow/_src/inference/hypotest/distributions.py
  • src/everwillow/_src/inference/hypotest/limit_solvers.py
  • src/everwillow/_src/inference/hypotest/results.py
  • src/everwillow/_src/inference/hypotest/test_statistics.py
  • src/everwillow/_src/inference/hypotest/toys.py
  • src/everwillow/_src/inference/hypotest/upper_limit.py
  • src/everwillow/_src/inference/hypotest/utils.py
  • src/everwillow/hypotest/__init__.py
  • src/everwillow/hypotest/calculators.py
  • src/everwillow/hypotest/limit_solvers.py
  • src/everwillow/hypotest/results.py
  • src/everwillow/hypotest/upper_limit.py
  • tests/inference/hypotest/_counting_model.py
  • tests/inference/hypotest/test_calculators.py
  • tests/inference/hypotest/test_distributions.py
  • tests/inference/hypotest/test_limit_solvers.py
  • tests/inference/hypotest/test_test_statistics.py
  • tests/inference/hypotest/test_toys.py
  • tests/inference/hypotest/test_upper_limit.py
  • tests/inference/hypotest/test_utils.py
💤 Files with no reviewable changes (3)
  • src/everwillow/hypotest/upper_limit.py
  • tests/inference/hypotest/test_upper_limit.py
  • src/everwillow/_src/inference/hypotest/upper_limit.py

Comment on lines 205 to 214
ToyGenerator(
test_statistic=QTilde(),
predict_fn=predict,
map_fn=lambda fn: partial(jax.lax.map, fn, batch_size=8),
)

# Python loop (no JIT, useful for step-through debugging)
ToyGenerator(
test_statistic=QTilde(),
predict_fn=predict,
map_fn=lambda fn: lambda keys: jnp.stack([fn(k) for k in keys]),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Define predict before using it.

The new ToyGenerator(predict_fn=predict, ...) example references no predict defined in this page. Add a minimal prediction function or explicitly state that it must be supplied from earlier user code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/extending-hypotest.md` around lines 205 - 214, Update the ToyGenerator
examples to define a minimal predict function before the first use of predict,
or clearly state that it must come from earlier user code. Ensure both map_fn
variants continue using the same defined prediction function.

Comment thread docs/multi-poi.md
Comment on lines +83 to +84
calc.upper_limit(BisectionLimitSolver(bounds=(0.0, 5.0)), poi_key="mu_a")
calc.upper_limit(BisectionLimitSolver(bounds=(0.0, 12.0)), poi_key="mu_b")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add imports for symbols used by the examples.

BisectionLimitSolver and plt are referenced but never imported in this guide. Add the corresponding limit-solver and Matplotlib imports before the examples.

Also applies to: 146-147

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/multi-poi.md` around lines 83 - 84, Add the missing imports for
BisectionLimitSolver and Matplotlib’s pyplot as plt before the examples in
docs/multi-poi.md, ensuring both references in the upper_limit examples are
defined.

Comment thread docs/multi-poi.md
Comment on lines +166 to +173
## Toy-based regions

`ToyCalculator.confidence_region` regenerates the toy ensembles at every
point, threading an independent subkey into each. The whole scan is
reproducible from the calculator's key:

```python
region = toy_calc.confidence_region(points, criterion=lambda r: r.pnull)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Define or explicitly assume toy_calc.

This page never constructs a ToyCalculator or imports it, so copying the toy-region example raises NameError. Add a short calculator construction, or state that toy_calc is an already-configured instance.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/multi-poi.md` around lines 166 - 173, Update the “Toy-based regions”
example to define toy_calc before calling confidence_region, either by adding a
concise ToyCalculator construction with the required import or by explicitly
stating that toy_calc is an already-configured instance. Ensure the example is
self-contained enough to avoid an undefined-name error.

Comment thread docs/quickstart.md
Comment on lines +231 to +232
# Toys are thrown at poi=1.0 under both hypotheses; p-values come from tail
# counting by default (swap distribution_factory for e.g. smoothed variants).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Distinguish null and alternative toy hypotheses.

With poi_alt=0.0, null toys use the tested POI (1.0 here), while alternative toys use poi_alt. The current wording says both ensembles match the tested POI, which is inaccurate for CLs. (raw.githubusercontent.com)

Also applies to: 294-297

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/quickstart.md` around lines 231 - 232, Update the quickstart
documentation around the toy-hypothesis descriptions to distinguish null toys,
which use the tested POI of 1.0, from alternative toys, which use poi_alt (0.0
here). Apply the same correction to the corresponding explanation near the
additionally referenced section, while preserving the existing tail-counting and
distribution_factory guidance.

Comment on lines 10 to 12
- ``AsymptoticCalculator``: Extends the base with Asimov dataset config
(``predict_fn``/``mu_asimov`` or ``asimov_observation``) for Cowan et al.
asymptotic workflows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale field name in module docstring. AsymptoticCalculator no longer exposes mu_asimov; the Asimov config field is poi_asimov (line 450). Update the module docstring to match.

📝 Proposed doc fix
 - ``AsymptoticCalculator``: Extends the base with Asimov dataset config
-  (``predict_fn``/``mu_asimov`` or ``asimov_observation``) for Cowan et al.
+  (``predict_fn``/``poi_asimov`` or ``asimov_observation``) for Cowan et al.
   asymptotic workflows.
📝 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
- ``AsymptoticCalculator``: Extends the base with Asimov dataset config
(``predict_fn``/``mu_asimov`` or ``asimov_observation``) for Cowan et al.
asymptotic workflows.
``AsymptoticCalculator``: Extends the base with Asimov dataset config
(``predict_fn``/``poi_asimov`` or ``asimov_observation``) for Cowan et al.
asymptotic workflows.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/everwillow/_src/inference/hypotest/calculators.py` around lines 10 - 12,
Update the module docstring entry for AsymptoticCalculator to replace the stale
mu_asimov field name with poi_asimov, while preserving the existing predict_fn
and asimov_observation references and description.

Comment on lines +283 to +291
converged = jnp.abs(mid_value - level) < self.tol

# Update the search bracket based on the middle value
# narrow it: if mid_value is below level, lo moves to mid; if above, hi moves to mid
# This assumes the objective is montonically decreasing with increasing POIs
new_lo = jnp.where(mid_value > level, mid, lo)
new_hi = jnp.where(mid_value <= level, mid, hi)

return iteration + 1, new_lo, new_hi, converged

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Bisection early-exit returns a POI offset from the converged point.

bisect always moves lo/hi to mid, including on the iteration where converged becomes True. After the loop exits, final_mid = (lo + hi) / 2 is therefore (mid + old_bound) / 2, not mid — an offset of up to a quarter of the current bracket. This contradicts the docstring pseudocode, which stops before updating lo/hi so that (lo + hi) / 2 == mid. The error is largest for flat objectives, where the value tolerance is met while the POI bracket is still wide, so a wrong limit is returned silently.

Freeze the bracket on the converged step so final_mid equals the evaluated mid:

🐛 Proposed fix
-                new_lo = jnp.where(mid_value > level, mid, lo)
-                new_hi = jnp.where(mid_value <= level, mid, hi)
+                new_lo = jnp.where(converged, lo, jnp.where(mid_value > level, mid, lo))
+                new_hi = jnp.where(converged, hi, jnp.where(mid_value <= level, mid, hi))
📝 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
converged = jnp.abs(mid_value - level) < self.tol
# Update the search bracket based on the middle value
# narrow it: if mid_value is below level, lo moves to mid; if above, hi moves to mid
# This assumes the objective is montonically decreasing with increasing POIs
new_lo = jnp.where(mid_value > level, mid, lo)
new_hi = jnp.where(mid_value <= level, mid, hi)
return iteration + 1, new_lo, new_hi, converged
converged = jnp.abs(mid_value - level) < self.tol
# Update the search bracket based on the middle value
# narrow it: if mid_value is below level, lo moves to mid; if above, hi moves to mid
# This assumes the objective is montonically decreasing with increasing POIs
new_lo = jnp.where(converged, lo, jnp.where(mid_value > level, mid, lo))
new_hi = jnp.where(converged, hi, jnp.where(mid_value <= level, mid, hi))
return iteration + 1, new_lo, new_hi, converged
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/everwillow/_src/inference/hypotest/limit_solvers.py` around lines 283 -
291, Update the bisect iteration logic around the converged flag so lo and hi
remain unchanged when the evaluated mid satisfies the tolerance; only narrow the
bracket when not converged. Preserve the existing directional updates for
non-converged iterations, ensuring the post-loop final_mid equals the converged
mid.

pnull: p-value under the tested hypothesis (poi_test).
palt: p-value under the alternative hypothesis (poi_alt / background-only).
test_stat_result: Full test statistic result with fit information.
dist: The distribution of the test statistic which was used to compute p-values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Docstring attribute name is stale. The Attributes entry is named dist, but the field is distribution. Rename for accuracy.

📝 Proposed doc fix
-        dist: The distribution of the test statistic which was used to compute p-values.
+        distribution: The distribution of the test statistic which was used to compute p-values.
📝 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
dist: The distribution of the test statistic which was used to compute p-values.
distribution: The distribution of the test statistic which was used to compute p-values.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/everwillow/_src/inference/hypotest/results.py` at line 132, Update the
docstring Attributes entry in the relevant results class to use the field name
distribution instead of the stale dist name, keeping the description unchanged.

Comment on lines +20 to +26
__all__ = [
"LimitSolver",
"StochasticLimitSolver",
"RootFindingLimitSolver",
"GridScanLimitSolver",
"BisectionLimitSolver",
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Sort __all__ to satisfy Ruff RUF022.

Use isort-style alphabetical ordering, for example BisectionLimitSolver, GridScanLimitSolver, LimitSolver, RootFindingLimitSolver, StochasticLimitSolver.

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 20-26: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/everwillow/hypotest/limit_solvers.py` around lines 20 - 26, Reorder the
entries in the __all__ declaration alphabetically to satisfy Ruff RUF022: place
BisectionLimitSolver, GridScanLimitSolver, LimitSolver, RootFindingLimitSolver,
and StochasticLimitSolver in that order.

Source: Linters/SAST tools

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.

1 participant