Repository navigation
Scenario_Theory
This page covers the parts of scenario theory that Scen-Opt implements. It explains what problem is solved and what the reported quantities mean: the complexity
The running example is the smallest enclosing interval (benchmarks/LP_half_width_2d). It takes
The uncertainty is a vector
In Scen-Opt, each scenario is one row of the scenarios file:
- In symbolic mode the row is
$\delta_i$ itself. Its components aredelta[0], ...,delta[q-1]. - In numeric mode the row holds the constraint data of that scenario, already evaluated. See First Example: Web Interface.
The i.i.d. assumption is the only statistical assumption, and it matters. The rows must be independent draws from the conditions the decision will face. If the data were filtered, hand-picked or correlated in time, the guarantees below do not apply.
All formulations in Scen-Opt are special cases of
The table shows what
| Program | Cost |
Scenario constraint | Hard constraint |
|---|---|---|---|
| LP | |||
| QP | |||
| SDP |
So
There is one slack per scenario. It is shared by all
Hard constraints do not depend on
- They are always enforced.
- They are not scenarios.
- They never count towards
$k$ . - They play no part in the risk.
| Web interface option | Slacks |
||
|---|---|---|---|
| Robust | – | none ( |
|
| Regularization | – | none | |
| Relaxation | yes | ||
| Regularization + Relaxation | yes |
-
Regularization pulls the solution towards a reference point
$\bar{x}$ (x_ref) in the$p$ -norm. -
Relaxation lets the program violate a scenario constraint by
$\zeta_i$ , at a cost of$\rho \zeta_i$ . - In the web interface, the selected option decides which of
$\tau$ and$\rho$ are used; the fields of the other options are ignored. - In the Python API, the formulation follows from the arguments
tau,rhoandinclude_slack. With the defaultinclude_slack=None, slacks are created exactly whenrho != 0. The regularization term is added only whentau > 0. - In the relaxation formulations
$\rho$ must be positive. With$\rho = 0$ the slacks cost nothing, so the scenario constraints have no effect. For the interval example, the solver then reports the problem as unbounded.
The theory assumes that the program has a solution and that the solution is unique. The usual ways to secure this are:
- With relaxation, the scenario constraints alone cannot make the program infeasible.
- Regularization (
$\tau > 0$ ) is the usual way to break ties between equally good solutions.
Scen-Opt decides support-list membership by comparing optimal values, not optimizers, so that test still works when the optimizer is not unique.
Below,
A decision
The risk of
For LP and QP this means at least one row of
The quantity of interest is
-
$V(x^\ast_N)$ cannot be computed, because$\mathbb{P}$ is unknown. -
$V(x^\ast_N)$ is itself random, because$x^\ast_N$ depends on the sample. - Scenario theory bounds
$V(x^\ast_N)$ using only$N$ , the complexity and the confidence parameter$\beta$ . No validation data and no model of$\mathbb{P}$ are needed.
A support list is a sublist of
- It contains every scenario that
$x^\ast_N$ violates ($f(x^\ast_N, \delta_i) > 0$ ), together with some of the scenarios at which$x^\ast_N$ is active ($f(x^\ast_N, \delta_i) = 0$ ). - Solving the program with only these scenarios (plus the hard constraints) gives back
$x^\ast_N$ . - It is irreducible: removing any element changes the solution.
More than one support list can exist. The smallest size among them is the complexity
- In Python, it is the
complexityreturn value ofsolve_lp,solve_qpandsolve_sdp. - In the web interface, it is the "Complexity (support list size)" row.
In the interval example, the two extreme points fix the interval, so
A violated scenario belongs to every support list, so
- A small
$\rho$ makes violations cheap. More scenarios are violated,$k$ grows, and so do the risk bounds. - A large
$\rho$ makes the program behave like the robust one.
The table shows this for the interval example. The settings are: Relaxation formulation,
| Half-width |
Violated scenarios | |||
|---|---|---|---|---|
| 0.02 | 0.2736 | 50 | 50 | [0.2367, 0.7638] |
| 0.05 | 0.4205 | 20 | 20 | [0.0433, 0.4719] |
| 0.1 | 0.4644 | 10 | 10 | [0, 0.3432] |
| 0.25 | 0.4911 | 4 | 4 | [0, 0.2476] |
| 1.0 | 0.4983 | 0 | 2 | [0, 0.2085] |
A narrower interval (lower cost) comes with a higher certified risk. The ρ and τ graphs in the web interface plot exactly this trade-off.
Regularization changes which solution is selected, and so it can change
The confidence parameter
For
and set
This one-sided result needs no assumption beyond those in Section 2 (Garatti & Campi, 2025).
For
It has exactly two solutions
For
It has one solution
The bounds are
If the problem is non-degenerate (Section 6), then
(Garatti & Campi, 2022).
src.Risk.quantify_risk(k, N, beta) returns (eps_lower, eps_upper), which are
- It rewrites the polynomial of Section 5.2 in the variable
$\varepsilon = 1 - t$ , using the regularized incomplete beta function$I_\varepsilon(a,b)$ (jax.scipy.special.betainc(a, b, eps)). - The two bounds are then the two zeros in
$[0,1]$ of
The zeros are found by bisection:
-
Lower bound. Bisect on
$[0, k/N]$ until the interval is narrower than$10^{-10}$ . Return the left end of the final interval, which is the conservative side. For$k = 0$ the interval is empty and$\underline{\varepsilon} = 0$ . -
Upper bound. If
$k = N$ , return$\overline{\varepsilon} = 1$ . Otherwise, bisect on$[k/N, 1]$ in the same way and return the right end, which is again the conservative side.
On test cases, these values match the roots of the polynomial in Section 5.2 found by direct root-finding, to about
quantify_risk computes in JAX 64-bit floats (it switches jax_enable_x64 on only while it runs). In float32, rounding noise could understate
The reported upper bound is also valid without non-degeneracy. Scen-Opt reports
Some example values of quantify_risk:
| 100 | 2 | 0 | 0.2085 | |
| 1000 | 2 | 0 | 0.0230 | |
| 10000 | 2 | 0 | 0.0023 | |
| 1000 | 20 | 0.0039 | 0.0561 | |
| 1000 | 100 | 0.0544 | 0.1629 | |
| 100 | 2 | 0 | 0.1404 | |
| 100 | 2 | 0 | 0.2680 |
The bounds bracket
"Risk
The two-sided bound needs the following property.
Non-degeneracy. For every
Equivalently, the scenarios that are active or violated at the optimum already form a support list: none of them is redundant. Degeneracy means more scenarios are active or violated than are needed to fix the solution. Typical causes:
- Repeated scenarios. Each copy can be dropped on its own, so several support lists exist. This can happen with discrete distributions or rounded data.
- Many constraints through one point. In the Growth Bound benchmark, essentially all of the 3127 scenario constraints are active at the optimum, but only 6 form a support list. In Radiation Therapy, 20 are active and 5 form a support list.
get_support (in src/Miscellaneous.py, details on Miscellaneous.py) computes
-
Screen. Keep each scenario constraint whose dual value exceeds
$10^{-8}$ . These are the active or violated ones, called the support scenarios. -
Check. Test whether these candidates alone, plus the hard constraints, reproduce the optimal value, to a relative tolerance of
$10^{-6}$ . -
Prune. If they do, remove candidates greedily while the optimal value stays the same. If the pruning removes a scenario that was genuinely active or violated at the optimum, raise the degeneracy flag.
- Interior-point solvers can leave a small dual on a constraint that is strictly slack. Removing such a constraint is treated as solver noise and does not raise the flag.
- When this happens, the console prints a "Discarded ... spuriously screened constraint(s)" message.
- Fallback. If the screened set does not reproduce the optimum, prune the full scenario list instead and raise the flag as a precaution. The resulting list is valid, but it cannot be certified to be the smallest one.
-
Python API: the last return value of
solve_lp,solve_qpandsolve_sdpisdegeneracy.quantify_riskdoes not know about it and always returns both bounds. IfdegeneracyisTrue, discardeps_lower. -
Web interface: the results table shows Degeneracy Detected? Yes, and the "Risk Bounds" cell shows the lower bound as N/A, e.g.
[N/A, 0.1414]. The ρ and τ graphs leave the lower bound of that run out. The downloaded results still contain the numerical lower bound inrisk; do not use it whendegeneracyis true. -
Benchmarks:
run.pyprints the bounds as[unreliable, <upper>]. The benchmark table in the README marks the lower bound as N/A (Growth Bound and Radiation Therapy).
In every case the upper bound
The check uses only the scenarios you supplied. It cannot prove that non-degeneracy holds with probability one. If the flag is not raised, it is still the user's responsibility to judge whether the lower bound applies.
Solver accuracy also matters. On a 200-point version of the interval example (the example on LP.py), the first-order solver SCS gives
The web interface accepts comma-separated lists for
- Each certificate fails with probability at most
$\beta$ , so the probability that any of them fails is at most$n_\tau n_\rho \beta$ (union bound). - All certificates therefore hold simultaneously with confidence at least
This is what allows you to look at all the results and then pick a
The interface reports this overall value:
- The confidence label changes to
$1 - \beta \cdot n_\rho$ ,$1 - \beta \cdot n_\tau$ or$1 - \beta \cdot n_\tau \cdot n_\rho$ , depending on the formulation. - The "Confidence" row of the results table shows the same value with all its digits (e.g. 0.999992), clamped at 0.
For example, 4 values of
For an overall confidence of
The Python API does no such bookkeeping. If you loop over several values yourself and compare the results, apply the same union bound.
The theory on this page is summarized from the works below.
- M. C. Campi and S. Garatti. Introduction to the Scenario Approach. SIAM, 2018.
- G. C. Calafiore and M. C. Campi. The scenario approach to robust control design. IEEE Transactions on Automatic Control, 51(5):742–753, 2006.
- M. C. Campi and S. Garatti. The exact feasibility of randomized solutions of uncertain convex programs. SIAM Journal on Optimization, 19(3):1211–1230, 2008.
- M. C. Campi and S. Garatti. Wait-and-judge scenario optimization. Mathematical Programming, 167(1):155–189, 2018.
- M. C. Campi and S. Garatti. Scenario optimization with relaxation: a new tool for design and application to machine learning problems. 2020 59th IEEE Conference on Decision and Control (CDC), pp. 2463–2468, 2020. doi:10.1109/CDC42340.2020.9303914
- M. C. Campi, A. Carè and S. Garatti. The scenario approach: A tool at the service of data-driven decision making. Annual Reviews in Control, 52:1–17, 2021.
- S. Garatti and M. C. Campi. Risk and complexity in scenario optimization. Mathematical Programming, 191(1):243–279, 2022.
- M. C. Campi and S. Garatti. Compression, generalization and learning. Journal of Machine Learning Research, 24(339):1–74, 2023.
- S. Garatti and M. C. Campi. Non-convex scenario optimization. Mathematical Programming, 209(1):557–608, 2025.