API reference¶
optimum_interval
¶
Optimum-interval upper limits in the presence of unknown background.
A small, dependency-light implementation of the method of S. Yellin, "Finding an Upper Limit in the Presence of Unknown Background", Phys. Rev. D66 (2002) 032005 (arXiv:physics/0203002).
The method sets a frequentist upper limit on a signal normalization (the
Poisson mean mu of expected events) using only the known signal shape,
without a model of the background and without binning. See EXPLANATION.md
for a physicist-oriented walk-through and derivation.
Quick start
import numpy as np from optimum_interval import OptimumIntervalTable rng = np.random.default_rng(0) table = OptimumIntervalTable(rng=rng) events = np.sort(rng.random(8)) # 8 events, already in cumulant space mu_limit = table.upper_limit(events, confidence=0.9, n=2000) # doctest: +SKIP
Public API
k_largest_intervals / cumulant_points
Pure interval geometry (:mod:optimum_interval.intervals).
c0 / x0 / max_gap_upper_limit / poisson_upper_limit
Analytic statistics: Yellin Eq. 2 and the counting limits
(:mod:optimum_interval.analytic).
OptimumIntervalTable
Monte-Carlo calibration tables and the upper-limit solver
(:mod:optimum_interval.montecarlo).
spectrum_cdf_from_pdf / spectrum_cdf_from_samples
Build a normalized spectrum_cdf (:mod:optimum_interval.spectra).
scan_extremeness / excluded_interval / spectrum_from_rate
Parameter scans when the spectrum shape depends on the parameter being
limited (:mod:optimum_interval.scanning).
ComparisonEngine
Fast per-experiment limits for method comparisons
(:mod:optimum_interval.comparison).
ComparisonEngine
¶
Precomputed-grid C_max and p_max upper-limit solver.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mu_grid
|
array_like
|
Grid of |
required |
n_cal
|
int
|
Monte-Carlo trials per grid point. |
40000
|
rng
|
Generator
|
Seed for reproducibility. |
None
|
confidence
|
float
|
Confidence level (default 0.9). |
0.9
|
Source code in optimum_interval/comparison.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
cmax_upper_limit(events, spectrum_cdf=None)
¶
Optimum-interval (C_max) upper limit on mu for one experiment.
Source code in optimum_interval/comparison.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | |
pmax_upper_limit(events, spectrum_cdf=None)
¶
p_max (Poisson-probability) upper limit on mu for one experiment.
Source code in optimum_interval/comparison.py
150 151 152 153 154 155 156 157 158 | |
OptimumIntervalTable
¶
Monte-Carlo interval-size tables plus the upper-limit solver.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rng
|
Generator
|
Random generator. Pass a seeded |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
itv_sizes |
dict[float, dict[int, ndarray]]
|
|
opt_itvs |
dict[float, ndarray]
|
|
n_trials |
dict[float, int]
|
Number of Monte-Carlo trials generated for each |
Source code in optimum_interval/montecarlo.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | |
generate_trials(mu, n)
¶
Draw n background-free trial experiments at Poisson mean mu.
Each trial is a sorted point list on [0, 1]: Poisson(mu) interior
uniforms plus the range endpoints 0 and 1. This mirrors exactly the
cumulant-space representation of real data (:func:cumulant_points).
Source code in optimum_interval/montecarlo.py
55 56 57 58 59 60 61 62 63 64 65 66 67 | |
generate(mu, n)
¶
Ensure tables for mu are built from at least n trials.
Populates :attr:itv_sizes, :attr:opt_itvs and :attr:n_trials.
Cached: returns immediately if mu already has >= n trials.
Source code in optimum_interval/montecarlo.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
extremeness_of_interval(x, k, mu)
¶
Fraction of MC k-largest intervals at mu smaller than x.
This is the code's empirical stand-in for :math:C_k(x, \mu). Returns
0 if no trial produced a k-largest interval (missing k).
Source code in optimum_interval/montecarlo.py
110 111 112 113 114 115 116 117 118 119 | |
optimum_interval_statistic(events, mu, spectrum_cdf=None)
¶
C_max of a run: the most extreme k-largest interval.
events are raw positions; they are transformed to cumulant space and
the range endpoints 0 and 1 are added (:func:cumulant_points) so the
calculation matches the Monte-Carlo calibration.
Source code in optimum_interval/montecarlo.py
121 122 123 124 125 126 127 128 129 130 131 132 | |
extremeness_of_opt_itv_stat(stat, mu)
¶
Fraction of MC C_max values at mu smaller than stat.
The confidence quantile of this distribution is bar_c_max.
Source code in optimum_interval/montecarlo.py
134 135 136 137 138 139 140 | |
bar_c_max(confidence, mu)
¶
Threshold :math:\bar C_\mathrm{max}(C, \mu) (Yellin Fig. 2).
The value of C_max reached with probability confidence under the
background-free hypothesis: the confidence quantile of
:attr:opt_itvs. generate must have been called for mu first.
Source code in optimum_interval/montecarlo.py
142 143 144 145 146 147 148 149 | |
upper_limit(events, confidence=0.9, spectrum_cdf=None, n=1000, *, mu_scan_start=1.0, mu_scan_stop=None, mu_scan_step=1.0)
¶
confidence-level optimum-interval upper limit on mu.
Finds mu such that extremeness_of_opt_itv_stat(C_max(events, mu),
mu) == confidence -- i.e. where the observed C_max equals
:math:\bar C_\mathrm{max}(\text{confidence}, \mu).
The excess extremeness - confidence increases with mu (a larger
proposed signal makes the observed emptiness look more anomalous). It is
evaluated on a fixed mu grid whose tables are cached on this instance,
and the zero crossing is interpolated between the bracketing grid points,
so the result is deterministic given the generator seed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
events
|
array_like
|
Observed event positions (raw; transformed via |
required |
confidence
|
float
|
Confidence level, e.g. |
0.9
|
spectrum_cdf
|
callable
|
Signal CDF mapping |
None
|
n
|
int
|
Monte-Carlo trials per grid |
1000
|
mu_scan_start
|
float
|
The |
1.0
|
mu_scan_stop
|
float
|
The |
1.0
|
mu_scan_step
|
float
|
The |
1.0
|
Returns:
| Type | Description |
|---|---|
float
|
The upper limit on |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If no exclusion is bracketed within the scan range; widen
|
Source code in optimum_interval/montecarlo.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | |
save(path)
¶
Pickle the tables to path (single dict, one payload).
Source code in optimum_interval/montecarlo.py
248 249 250 251 252 253 254 255 256 | |
load(path, rng=None)
classmethod
¶
Load tables previously written by :meth:save.
Source code in optimum_interval/montecarlo.py
258 259 260 261 262 263 264 265 266 267 268 269 | |
c0(x, mu)
¶
Probability the maximum gap is smaller than x (Yellin Eq. 2).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
Maximum-gap size in expected events ( |
required |
mu
|
float
|
Total expected number of events over the analysis range. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
:math: |
Source code in optimum_interval/analytic.py
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | |
max_gap_upper_limit(max_gap_fraction, confidence=0.9)
¶
Maximum-gap (:math:C_0) upper limit on mu for one experiment.
Solve :math:C_0(\mu\,f, \mu) = C for mu, where f is the observed
maximum-gap size as a fraction of the range (its expected-event size is
mu * f). C_0 increases with mu here, so the root is unique.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_gap_fraction
|
float
|
Largest gap between adjacent events (including the range endpoints) in
cumulant space, in |
required |
confidence
|
float
|
Confidence level, e.g. |
0.9
|
Source code in optimum_interval/analytic.py
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | |
poisson_upper_limit(n_observed, confidence=0.9)
¶
Classical Poisson upper limit on the mean given n_observed events.
The standard total-count limit: mu_up is the mean for which a fraction
confidence of experiments would see more than n_observed events,
i.e. :math:\sum_{k=0}^{n} e^{-\mu_\text{up}}\mu_\text{up}^k/k! = 1-C. This
is the inverse incomplete Gamma function gammaincinv(n+1, confidence).
(Used only for the method-comparison figures; e.g. n=0 gives 2.3026.)
Source code in optimum_interval/analytic.py
152 153 154 155 156 157 158 159 160 161 | |
x0(confidence, mu)
¶
Invert :func:c0: the gap size at which c0(x, mu) == confidence.
This is Yellin's :math:x_0(C, \mu). For a max-gap 90% upper limit one
raises mu until the observed gap equals x0(0.9, mu).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
confidence
|
float
|
Target confidence level, e.g. |
required |
mu
|
float
|
Total expected number of events. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The gap size |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in optimum_interval/analytic.py
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | |
cumulant_points(events, spectrum_cdf=None)
¶
Map observed events to cumulant space and append the range endpoints.
This is the transform every real measurement must undergo before calling
:func:k_largest_intervals: apply the (normalized) spectrum CDF so the
signal becomes uniform on [0, 1], then add the experimental-range
boundaries 0 and 1 (which act as interval delimiters exactly like events).
The Monte-Carlo path builds equivalent point lists directly (interior uniforms plus 0 and 1), so the two paths are consistent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
events
|
array_like
|
Raw event positions (e.g. recoil energies) inside the analysis range. |
required |
spectrum_cdf
|
callable
|
Monotonic CDF mapping |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Sorted cumulants with 0 prepended and 1 appended. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the resulting cumulants fall outside |
Source code in optimum_interval/intervals.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | |
k_largest_intervals(points, spectrum_cdf=None)
¶
Size of the largest interval containing exactly k events, for each k.
An "interval" is a stretch of the parameter (energy) axis; its size is
the expected fraction of signal events it contains. The k-largest
interval of a run is the largest interval that happens to contain exactly
k observed events -- i.e. an unusually empty region. Intervals are
delimited by observed events (and, for a real measurement, by the range
endpoints, which the caller must include; see :func:cumulant_points).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
array_like
|
1-D event positions. If |
required |
spectrum_cdf
|
callable
|
Monotonic CDF mapping raw positions to cumulants. Applied after sorting. Defaults to the identity. |
None
|
Returns:
| Type | Description |
|---|---|
dict[int, float]
|
Mapping |
Notes
For a sorted point list p the largest interval containing k events
is max_i (p[i + k + 1] - p[i]) -- the widest span between two points
that are k + 1 apart in the sorted order (hence k points strictly
between them). k = 0 reduces to the maximum gap of the max-gap method.
Examples:
>>> k_largest_intervals(np.array([0.0, 0.1, 0.2, 0.84, 0.85]))[0]
0.64
Source code in optimum_interval/intervals.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | |
excluded_interval(params, extremeness, level=0.95)
¶
(low, high) edges of the excluded interval along a 1-D parameter scan.
extremeness is the :func:scan_extremeness value at each of the
(positive, increasing) params; edges are log-interpolated crossings
of level. Returns (nan, nan) when nothing is excluded; an edge
saturates at the end of the scan if the crossing lies beyond it.
Source code in optimum_interval/scanning.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | |
new_table(seed=0)
¶
A seeded Monte-Carlo calibration table (reusable across a whole scan).
Source code in optimum_interval/scanning.py
35 36 37 | |
round_log(x, dex=0.02)
¶
Round onto a dex-spaced log grid so tables are shared between points.
Source code in optimum_interval/scanning.py
40 41 42 | |
scan_extremeness(table, events, x, rate, exposure, n=2500, mu_floor=0.2, mu_cap=40.0, mu_dex=0.02)
¶
Optimum-interval extremeness of events for one scan point's spectrum.
Returns (p, mu) where p is the probability that a background-free
pseudo-experiment under this hypothesis looks less extreme than the
data (so the limit is where p crosses the confidence level), and
mu the expected counts. Events outside the spectrum's support window
are dropped automatically: they cannot be signal at this scan point.
Shortcuts: mu < mu_floor returns p = 0 (nothing expected);
mu > mu_cap returns p = 1 (with few observed events the exclusion
is overwhelming; no Monte Carlo needed — raise the cap if you scan with
many events). mu is rounded onto a mu_dex log grid so calibration
tables are reused across the scan.
Source code in optimum_interval/scanning.py
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
spectrum_from_rate(x, rate, exposure)
¶
Expected counts and normalized spectrum CDF from a differential rate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
arrays
|
Differential rate |
required |
rate
|
arrays
|
Differential rate |
required |
exposure
|
float
|
Livetime in the inverse time unit of |
required |
Returns:
| Type | Description |
|---|---|
(mu, cdf, x_lo, x_hi), or ``None`` if the rate has no support.
|
|
Source code in optimum_interval/scanning.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
spectrum_cdf_from_pdf(pdf, e_min, e_max, n_grid=2048)
¶
Normalized CDF on [e_min, e_max] from a signal density pdf.
pdf need not be normalized -- only its shape matters (the overall rate,
i.e. mu, is what the method limits and must not be folded in). The
density is cumulatively integrated (trapezoidal) on a fine grid and the
result is normalized so cdf(e_min) == 0 and cdf(e_max) == 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pdf
|
callable
|
Non-negative density |
required |
e_min
|
float
|
Analysis-window bounds. |
required |
e_max
|
float
|
Analysis-window bounds. |
required |
n_grid
|
int
|
Integration/interpolation grid size. |
2048
|
Source code in optimum_interval/spectra.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
spectrum_cdf_from_samples(energies, cdf_values)
¶
Normalized spectrum_cdf interpolating tabulated (energy, CDF) points.
Useful when the CDF is only known at sampled energies (e.g. from a
simulation). cdf_values must be non-decreasing; they are rescaled so the
endpoints map to exactly [0, 1].
Source code in optimum_interval/spectra.py
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | |