VSS: Value of the Stochastic Solution report (--vss, two-stage) - #791
Draft
DLWoodruff wants to merge 6 commits into
Draft
VSS: Value of the Stochastic Solution report (--vss, two-stage)#791DLWoodruff wants to merge 6 commits into
DLWoodruff wants to merge 6 commits into
Conversation
Add doc/designs/vss_design.md: a post-run --vss report in generic_cylinders that computes VSS = EEV - RP. Reuses shipped machinery (average_scenario_creator for the mean-value solve; Xhat_Eval.evaluate for the honest cross-scenario EEV; wheel/EF for RP), mirroring the do_mmw post-run report structure. Covers the minimization/maximization sign convention, the EF-exact vs decomposition-bracketed RP, the infeasible-mean-value-solution case, and a prominent warning that EEV re-solves every scenario and can roughly double run time. Two-stage first; multistage plumbing (ef_xhat_nonants) noted as future. Closes the pysp_but_not_mpisppy.md A7 gap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-stage) generic_cylinders --vss prints RP / EV / EEV / VSS after an --EF or a decomposition run. Reuses shipped machinery: average_scenario_creator for the mean-value solve (x_bar) and Xhat_Eval.evaluate for the honest cross-scenario EEV; RP is exact from the EF or the incumbent (with a BestOuterBound/BestInnerBound bracket) from the wheel. Structure mirrors do_mmw: new mpisppy/generic/vss.py (vss_prep + do_vss), one config flag, two call sites in generic_cylinders. - Sign-aware: VSS = EEV-RP (min) or RP-EEV (max), always reported >= 0. - Decomposition run reports a conservative point value plus the VSS bracket implied by the unclosed optimality gap. - Infeasible mean-value first stage => EEV = VSS = +inf, naming the offending scenarios (MPI-safe: solves with need_solution=False so a clean per-scenario infeasibility does not deadlock the collectives). - Two-stage only; fails fast at setup (before the solve) on multistage, proper bundles, ADMM, --cvar, or a missing average_scenario_creator. Tests: mpisppy/tests/test_vss.py (guards, sign/bracket helpers, EF min/max e2e, decomposition bracket, infeasible EEV), wired into run_coverage.bash and test_pr_and_main.yml. Docs: doc/src/vss.rst (+ index toctree); pysp_but_not_mpisppy.md A7 marked addressed for two-stage. Design in doc/designs/vss_design.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The unit-tests job installs no solver, so test_vss's solver-gated e2e cases (the ones asserting the actual RP/EV/EEV/VSS numbers on farmer) would skip there. Add a dedicated step in the cplex/xpress regression job -- mirroring test_feasible_xhat -- so the numerical results are actually checked in CI. The farmer-3 cases are LPs with a unique, solver-independent optimum, well within community-cplex size limits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gression job Like test_vss, test_jensens was only in the no-solver unit-tests job, so its solver-gated end-to-end cases (farmer-3 average-scenario bound, sizes-3 integer-guard paths) skipped in CI. Add a dedicated regression-job step so they actually run under cplex/xpress. Both models are small and within community-cplex limits; the file is green locally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #791 +/- ##
==========================================
+ Coverage 76.16% 76.24% +0.07%
==========================================
Files 169 170 +1
Lines 22224 22396 +172
==========================================
+ Hits 16928 17076 +148
- Misses 5296 5320 +24 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… solves The EV and EEV solves were passing no solver options, so they ran at the solver's default mipgap -- inconsistent with the run that produced RP, and uncontrollable. Resolve solver name + options via solver_specification (EF_solver_options after --EF, else solver_options) and apply them to both the mean-value solve and the Xhat_Eval EEV pass. Also lets --vss work on an --EF run that sets only --EF-solver-name. Because VSS = EEV - RP is a difference of two optimized values, mipgap matters; document that the VSS solves inherit the run's solver-options string and that a loose gap makes a small VSS unreliable. Also reconcile the cost docs: drop the "roughly doubles run time on large/ integer models" claim (backwards -- fixing the first stage decouples the scenarios and makes each EEV solve easier; for decomposition EEV is ~one iteration's worth). State honestly that the added time is model-dependent and can be significant for some problems. Adds test_ef_solver_options_threaded; updates _compute_eev's signature and its infeasible-path test caller. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get_objective_value() returns the EF solver's incumbent, which for a MIP solved to a nonzero gap is only known to within that gap -- but do_vss unconditionally labelled RP "[EF, exact]", contradicting the doc's own mipgap warning. The dual bound existed at solve time (results.problem lower/upper_bound) but do_EF discarded it. do_EF now stashes ef.ef_objective_bounds; do_vss reads the sense- appropriate dual bound and, when the relative gap exceeds 1e-9, relabels RP "EF incumbent, gap=..." and brackets VSS with the existing bracket machinery (same inner/outer semantics as the decomposition case). LP, zero-gap, or no-bound-reported keeps "EF, exact" with no bracket. Docs (vss.rst + module docstring) updated for the exact-vs-bracketed distinction. Adds helper unit tests plus solver-gated end-to-end cases for the no-bound, nonzero-gap, and zero-gap paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a Value of the Stochastic Solution (VSS) report to
generic_cylinders. Passing--vssprintsRP / EV / EEV / VSSat theend of an
--EFor a decomposition run. Two-stage, opt-in, report-only(it never changes the solution). Closes the two-stage part of the
pysp_but_not_mpisppy.md§A7 gap (PySP'sef_vss.py).Example (farmer, 3 scenarios):
How
Mirrors the
do_mmwpost-run report and reuses shipped machinery — nonew spoke/hub code:
average_scenario_creator(the Jensen's-bound contract) → the mean-valuesolve, giving
EVand the first-stagex_bar;Xhat_Eval.evaluate→ the honest cross-scenarioEEV(fixx_bar,solve every scenario);
RPfromef.get_objective_value()— exact when the EF is solved to azero gap (always for an LP), otherwise the incumbent bracketed by the
solver's dual bound; or, from a decomposition run, the incumbent
(
BestInnerBound) with aBestOuterBoundbracket from the wheel.New
mpisppy/generic/vss.py(vss_prep+do_vss), one config flag, twocall sites in
generic_cylinders.py.Behavior details
VSS = EEV − RP(min) orRP − EEV(max), alwaysreported ≥ 0.
--EFrun reportsRPas[EF, exact]onlywhen the solver reaches a zero gap (an LP, or a MIP solved tight). A MIP
left with a nonzero mipgap returns only an incumbent, so
RPis relabelled[EF incumbent, gap=…]and bracketed by the solver's dual bound — the samepoint-plus-bracket treatment as the decomposition case below. (
do_EFstashes the EF objective bound for this; a solver that reports no bound
falls back to
[EF, exact].)the VSS bracket implied by any unclosed optimality gap.
and
solver_optionsviasolver_specification—EF_solver_optionsafter
--EF, elsesolver_options— so all three numbers are solvedconsistently and any
mipgapin that string applies. SinceVSS = EEV − RPis a difference of two optimized values, a loose gap makes a small VSS
unreliable (documented). (Also lets
--vsswork on an--EFrun thatsets only
--EF-solver-name.)EEV = VSS = +inf, naming the offending scenarios. MPI-safe: solves withneed_solution=Falseso a clean per-scenario infeasibility does notdeadlock the collectives.
ADMM,
--cvar, or a missingaverage_scenario_creator.Cost
EEVre-solves every scenario once with the first stage fixed. Fixing thefirst stage decouples the scenarios and makes each solve easier than the
original, so the added wall-clock is model-dependent — usually a fraction
of the run (for a decomposition run, ~one iteration's worth), occasionally
significant with very many scenarios or expensive recourse. Off by default.
Tests
mpisppy/tests/test_vss.py: setup guards, sign/bracket helpers, EFminimize and maximize end-to-end (asserting the actual RP/EV/EEV/VSS
numbers), solver-options threading, the EF RP labelling (exact / nonzero-gap
bracket / zero-gap) and its dual-bound helpers, the decomposition bracket,
and the infeasible-EEV path. Wired into
run_coverage.bashand a dedicated step inthe solver-equipped
regressionCI job so the numerical assertions rununder cplex/xpress (not just the no-solver unit job).
Drive-by:
test_jensens.pyhad the same wiring gap (only in theno-solver job); added a
regression-job step so its solver-gated e2e casesrun in CI too.
Docs
doc/src/vss.rst(in the toctree; explains VSS, the honest cost note, thesolver-options/mipgap reuse, the EF-exact vs bracketed-RP distinction, and
infeasibility);
doc/designs/vss_design.md(design);pysp_but_not_mpisppy.md§A7 marked addressed for two-stage.Scope / follow-ups
Two-stage only. Multistage VSS (the expected-value tree via
ef_xhat_nonants) and EVPI are noted as future work in the design doc. V1threads the solver-options string (not the separate
EF_mipgap/*_iter*_mipgapknobs or a*_solver_options_file).🤖 Generated with Claude Code