Ben Wooding, Simone Garatti, Marco C. Campi and Abolfazl Lavaei
Scen-Opt is an open-source software tool for data-driven convex optimization using the scenario approach of Campi and Garatti. It solves Linear Programs (LP), Quadratic Programs (QP), and Semidefinite Programs (SDP) using only sampled uncertainty realizations, providing rigorous probabilistic guarantees on out-of-sample performance without requiring knowledge of the underlying probability distribution.
The tool is implemented as a Python Flask web application with a modern JavaScript frontend, offering an intuitive graphical interface for specifying problems, uploading data, and inspecting results. It supports 27+ convex optimization solvers through CVXPY, including MOSEK, CLARABEL, SCS, and OSQP.
Prerequisites: Python 3.10 or newer (3.12 recommended) and pip.
# Clone the repository
git clone https://github.com/Kiguli/Scen-Opt.git
cd Scen-Opt
# Install dependencies
pip install -r requirements.txt
# Start the application
python3 app.pyThe server starts at http://127.0.0.1:5000. Open this URL in a browser to access the web interface.
MOSEK: MOSEK is the recommended solver: it is the most stable for these problems and was used for all published results. Its Python package is installed by requirements.txt, but it needs a licence (free for academics). See MOSEK Support for license configuration.
- Run
python3 app.pyand openhttp://127.0.0.1:5000 - Select the program type tab (LP, QP, or SDP)
- Enter or upload constraint matrices, objective vectors, and scenarios
- Choose a solver (if using MOSEK, you will be prompted to upload a license file)
- Set solver parameters and press Solve
- Download results as
.jsonor.matusing the buttons in the results panel
Every benchmark under benchmarks/*/data/ ships two self-contained JSON files that can be pasted straight into the UI:
program_symbolic.json— matrices usedelta[k]expressions. Pair with the benchmark'sscenarios.csvand pick Symbolic input mode.program_numeric.json— matrices are pre-evaluated per sample. Pair withscenarios_numeric.csv(each row carries row-wise flattenedA_i | b_ifor LP/QP, orF_{0,i} | F_{1,i} | … | F_{d,i}for SDP) and pick Numeric input mode.
Steps:
- Click Upload Program (next to the LP/QP/SDP tabs).
- Paste the contents of
program_symbolic.json(orprogram_numeric.json) into the paste box, or select the file in the upload input. - Under Scenarios, upload the matching
scenarios.csv(symbolic) orscenarios_numeric.csv(numeric). - Press Solve.
Each program type supports four configurations: robust, robust + relaxation, robust + regularization, and the general form combining all three.
min c'x + tau * ||x - x_ref||_p + rho * sum(zeta)
s.t. A(delta_i) x + b(delta_i) <= zeta_i, i = 1,...,N (scenario constraints)
G x + h <= 0 (hard constraints)
zeta_i >= 0
min (1/2) x'Qx + c'x + tau * ||x - x_ref||_p + rho * sum(zeta)
s.t. A(delta_i) x + b(delta_i) <= zeta_i, i = 1,...,N (scenario constraints)
G x + h <= 0 (hard constraints)
zeta_i >= 0
where Q is positive semidefinite.
min (1/2) x'Qx + c'x + tau * ||x - x_ref||_p + rho * sum(zeta)
s.t. F_0(delta_i) + sum_j x_j F_j(delta_i) <= zeta_i * I, i = 1,...,N (scenario LMIs)
E_0 + sum_j x_j E_j <= 0 (hard LMI)
where the scenario constraints are Linear Matrix Inequalities (LMIs) parameterized by uncertainty samples.
| Parameter | Symbol | Description |
|---|---|---|
| Scenarios | delta_i | Sampled uncertainty realizations (N samples) |
| Slack penalty | rho | Penalty on constraint relaxation (rho = 0 means hard scenario constraints) |
| Regularization | tau | Regularization strength toward reference point x_ref |
| Norm type | p | Norm for regularization (any p ≥ 1, inf, or fro) |
| Confidence | beta | Confidence parameter for risk bounds (e.g. beta = 0.01 for 99% confidence) |
After solving, the tool computes the complexity k (number of support constraints) and the scenario approach risk bounds [epsilon_lower, epsilon_upper]. These bound the probability that a new unseen scenario would violate the solution, providing certified probabilistic guarantees on out-of-sample performance.
After solving, two buttons appear in the results panel:
- Download .JSON -- Downloads the full result data (optimal solution, cost, risk bounds, timing, etc.) as a JSON file directly from the browser.
- Download .MAT -- Downloads the same data as a MATLAB
.matfile, generated server-side.
The results include optimization solve time and risk computation time alongside the standard outputs.
The benchmarks/ directory contains 12 case studies spanning all three program types, each with pre-generated data, solver scripts, and paper figures.
| Benchmark | Type | d | q | m_s | m_h | rho | tau | N | beta | k | epsilon_lower | epsilon_upper | Time (s) | Data Source |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Half Width | LP | 2 | 1 | 2 | 0 | 0 | 0 | 100 | 1e-6 | 2 | 0 | 0.2085 | 1.2 | --- |
| Growth Bound | LP | 12 | 6 | 3 | 6 | 0 | 0 | 3127 | 1e-6 | 6 | N/A | 0.0103 | 1276.8 | --- |
| Inventory | LP | 11 | 15 | 6 | 17 | 100 | 0 | 500 | 1e-6 | 7 | 0 | 0.0666 | 4.0 | --- |
| Portfolio CVaR | LP | 13 | 12 | 4 | 27 | 0.016 | 0 | 1255 | 1e-6 | 3 | 0 | 0.0203 | 22.0 | Yahoo Finance |
| Power Dispatch | LP | 120 | 48 | 48 | 330 | 100 | 0 | 150 | 1e-6 | 34 | 0.0791 | 0.4496 | 2.9 | --- |
| Iris SVM | QP | 3 | 3 | 1 | 0 | 0 | 0 | 150 | 1e-6 | 2 | 0 | 0.1441 | 1.2 | UCI Iris |
| Robot Navigation | QP | 118 | 1 | 3 | 320 | 0 | 0 | 500 | 1e-6 | 1 | 0 | 0.0403 | 1.6 | --- |
| Radiation Therapy | QP | 50 | 3 | 80 | 100 | 0 | 0.1 | 200 | 1e-6 | 5 | N/A | 0.1414 | 112.3 | TROTS |
| LPV Stability | SDP | 3 | 1 | 2 | 2 | 1 | 0 | 100 | 1e-6 | 1 | 0 | 0.1858 | 1.7 | --- |
| Quadratic Stability | SDP | 6 | 2 | 3 | 3 | 0 | 0 | 500 | 1e-6 | 1 | 0 | 0.0403 | 2.2 | --- |
| Covariance Estimation | SDP | 15 | 5 | 5 | 5 | 0 | 0 | 178 | 1e-6 | 7 | 0 | 0.1780 | 2.1 | UCI Wine |
| Min. Encl. Ellipsoid | SDP | 15 | 5 | 1 | 5 | 0 | 0 | 569 | 1e-6 | 5 | 0 | 0.0518 | 235.6 | UCI Breast Cancer |
d = decision variables, q = uncertainty dimension, m_s = scenario constraints per sample, m_h = hard constraints, N = number of scenarios, k = complexity (support constraints). N/A = degeneracy detected, so the theoretical lower risk bound is not certified. Solve times on a desktop with an Intel Core Ultra 9 285K (24 cores) and 64 GB RAM running Windows 11.
Linear Programs:
- Half Width (
LP_half_width_2d) -- Find the smallest interval enclosing 100 points sampled from [0, 1]. A minimal introductory example with 2 decision variables. - Growth Bound (
LP_growth_bound_12d) -- Compute a data-driven growth bound for finite abstraction of a 3D vehicle dynamics system from 3127 sampled trajectories (Kazemi et al. 2024). Degeneracy is detected: essentially all scenario constraints are active at the optimum while only 6 form the support list, so the lower risk bound is not certified. - Inventory (
LP_inventory_11d) -- Optimize daily ordering of 5 perishable produce items under uncertain yield, demand, and warehouse packing. Budget $12,000, warehouse 800 cu.ft. - Portfolio CVaR (
LP_portfolio_cvar_13d) -- Minimize Conditional Value-at-Risk (95%) for a pension fund across 8 ETF asset classes using the Rockafellar-Uryasev formulation. Real market data from Yahoo Finance. - Power Dispatch (
LP_power_dispatch_120d) -- Schedule 3 thermal generators over 24 hours to meet demand with uncertain wind and solar generation. 120 decision variables, 330 hard constraints.
Quadratic Programs:
- Iris SVM (
QP_Iris_minimal_3d) -- Hard-margin SVM separating Iris Setosa from other species using petal measurements from the UCI Iris dataset. - Robot Navigation (
QP_cbf_robot_navigation_118d) -- Navigate a point robot from start to goal around a wall obstacle with uncertain wall position. 20 timesteps, double-integrator dynamics, 118 decision variables. - Radiation Therapy (
QP_radiation_therapy_50d) -- Design a prostate brachytherapy plan under catheter placement uncertainty using TROTS dose-influence data. 50 dwell positions, 100 voxels, 80 dose constraints per scenario. Degeneracy is detected: 20 scenario constraints are active at the optimum while only 5 form the support list, so the lower risk bound is not certified.
Semidefinite Programs:
- LPV Stability (
SDP_LPV_stability_3d) -- Certify quadratic stability of a 2x2 LPV damped oscillator by finding a common Lyapunov matrix P over the scheduling parameter range. - Quadratic Stability (
SDP_Quadratic_Stability_6d) -- Find a Lyapunov matrix for a 3x3 coupled oscillator with two uncertain parameters, certifying stability over a 2D parameter space. - Covariance Estimation (
SDP_covariance_wine_15d) -- Compute a robust minimum-trace covariance matrix dominating all subsample covariances drawn from the UCI Wine dataset (5 features). - Min. Encl. Ellipsoid (
SDP_ellipsoid_enclosure_15d) -- Find the tightest ellipsoid containing data points from the UCI Breast Cancer dataset (5 features, 569 samples).
Each benchmark follows a standardized layout:
<benchmark_name>/
├── README.md Problem description, formulation, and usage
├── parameters.txt Solver parameters (rho, tau, confidence)
├── generate.py Generate/regenerate scenarios and data
├── run.py Solve the program and print results
├── plot.py Generate the paper figure
├── data/
│ ├── program_symbolic.json Upload-Program definition (symbolic mode)
│ ├── program_numeric.json Upload-Program definition (numeric mode)
│ ├── scenarios.csv Sampled uncertainty realizations
│ ├── scenarios_numeric.csv Per-row-flattened matrices for numeric mode
│ └── ... Additional problem-specific data
└── results/
├── metrics.json Solver output (cost, risk bounds, complexity)
├── solution.csv Optimal decision vector
└── *.png / *.pdf Paper figures
cd benchmarks/<benchmark_name>
# Regenerate scenarios (optional, data already provided; some generators need extra packages, listed at the top of the script)
python generate.py
# Solve the program (MOSEK by default; e.g. --solver CLARABEL without a MOSEK licence)
python run.py
# Generate the paper figure (needs matplotlib: pip install matplotlib)
python plot.pyScen-Opt/
├── app.py Flask application (routes, matrix parsing, solver dispatch)
├── requirements.txt Python dependencies
├── Dockerfile Container image for local deployment
├── DOCKER.md Docker setup instructions
├── src/
│ ├── LP.py Linear programming solver
│ ├── QP.py Quadratic programming solver
│ ├── SDP.py Semidefinite programming solver
│ ├── Risk.py Scenario approach risk bound computation
│ ├── Miscellaneous.py Utilities (active constraint detection, file loading)
│ ├── parsing.py Shared matrix/tensor expression parsing
│ └── mosek_solve.py Subprocess entry point for MOSEK solves
├── templates/
│ └── index.html Web interface (single-page application)
├── static/
│ ├── css/styles.css Stylesheet
│ └── js/main.js Frontend logic (matrix editors, file upload, results display)
├── benchmarks/ 12 benchmark case studies (see above)
└── tests/ pytest test suite (42 tests)
The main entry point. Routes:
GET /-- Serves the web interface with available solver listPOST /solve-- Parses uploaded matrices and scenario data, dispatches to the appropriate solver, and returns results as JSON. When a MOSEK license is uploaded, the solve runs in an isolated subprocess for concurrent user support. Results includesolve_timeandrisk_timefields.POST /parse-file-- Parses uploaded binary files (MAT, Excel, Parquet) and returns content as JSON for the frontendPOST /download-mat-- Converts JSON result data to a MATLAB.matfile download
Supports matrix expressions containing the delta variable (e.g., [[delta[0] + delta[1], 0], [0, 1]]) that are evaluated at each scenario point. Accepts file uploads in JSON, CSV, TXT, TSV, NPY, NPZ, MAT, Excel, and Parquet formats.
Solves the scenario LP using CVXPY. Takes scenario-dependent constraint functions A_d(delta) and b_d(delta), hard constraint matrices G and h, objective vector c, and parameters rho, tau, x_ref, norm_type. When rho > 0, introduces non-negative slack variables zeta with per-scenario relaxation. Returns the optimal solution x, slacks zeta, cost, number of scenarios, complexity, constraints, and degeneracy flag.
Extends the LP solver with a quadratic objective (1/2) x'Qx. Validates that Q is positive semidefinite and symmetric before solving. Same constraint structure and return values as the LP solver.
Solves semidefinite programs with LMI constraints. Each scenario produces a matrix inequality F_0(delta) + sum_j x_j F_j(delta) <= zeta_i * I. The hard constraint is an LMI E_0 + sum_j x_j E_j <= 0. All F and E matrices must be symmetric. Uses scalar decision variables x (not matrix variables).
Computes the scenario approach risk bounds given the complexity k (support constraint count), number of scenarios N, and confidence parameter beta. Uses the regularized incomplete beta function via JAX to compute both lower and upper bounds on the violation probability epsilon through bisection. Returns (epsilon_lower, epsilon_upper).
Identifies the support list of a solved scenario program: the constraints whose removal changes the optimal value (all violated constraints together with an irreducible subset of the active ones). Candidates are screened by dual value, then greedily pruned by re-solving and comparing the optimal value (via the helper test_support()); hard constraints are always enforced and never counted. If the screened set fails to reproduce the optimum, a recovery pass restarts from the full scenario list and flags degeneracy. Also provides get_solvers() to list installed CVXPY solvers and load_file() for reading data files.
| Format | Extensions | Usage |
|---|---|---|
| CSV | .csv |
Matrices, vectors, scenarios |
| JSON | .json |
One-shot program definitions, matrices |
| MATLAB | .mat |
One-shot program definitions |
| Text | .txt, .tsv |
Matrices, parameter files |
| NumPy | .npy, .npz |
Arrays |
| Excel | .xlsx, .xls |
Matrices, scenarios |
| Parquet | .parquet |
Large datasets |
MOSEK is a commercial solver with free academic licenses that is particularly effective for SDP problems. Scen-Opt supports MOSEK with the following features:
- License upload: When MOSEK is selected as the solver, a modal prompts you to upload your
mosek.licfile. The license is not stored on the server. - Browser caching: After the first upload, the license is cached in your browser's session storage. Subsequent solves skip the modal automatically. A cache indicator and "Clear" button appear below the solver dropdown.
- Local license: If MOSEK is installed locally with a system license (e.g., at
~/mosek/mosek.lic), click "I have a local license" in the modal to use it directly. - Concurrent users: Each MOSEK solve with an uploaded license runs in an isolated subprocess with the license written to an ephemeral
/tmpdirectory (RAM-backed, auto-purged). Multiple users can solve simultaneously without conflicts.
The easiest way to run Scen-Opt locally without managing Python dependencies is with Docker. A pre-built image is published on the GitHub Container Registry:
# Latest version
docker run -p 5000:5000 ghcr.io/kiguli/scen-opt:latest
# Exact release, e.g. v1.0
docker run -p 5000:5000 ghcr.io/kiguli/scen-opt:v1.0To build the image yourself instead:
docker build -t scen-opt .
docker run -p 5000:5000 scen-optOpen http://localhost:5000 in your browser. See DOCKER.md for MOSEK license mounting, custom port mapping, and worker configuration.
The test suite uses pytest and covers all core modules: solvers (LP, QP, SDP), risk quantification, matrix/tensor parsing, file loading utilities, and Flask route integration.
pip install pytest
python -m pytest tests/ -vTests run automatically on push and pull request via GitHub Actions (see .github/workflows/test.yml).
Scen-Opt is released under the MIT License.
This work was supported in part by an EPSRC Doctoral Prize Research Fellowship at Newcastle University.