CVaR Phase 2/3: CLI + decomposition, maximize support, and docs#751
Conversation
Adds the command-line / generic_cylinders surface for the CVaR transform and completes maximize support, so risk aversion is available end-to-end. - config: cvar_args() adds --cvar / --cvar-weight / --cvar-alpha / --cvar-mean-weight; wired into generic/parsing.py. - generic_cylinders: when --cvar is set, wrap scenario_creator with cvar_scenario_creator (guarded against bundles/ADMM). eta becomes just another first-stage variable, so EF and every cylinder inherit risk aversion with no algorithm changes. - cvar.py: maximize support via the PySP lower-tail mirror -- the excess var becomes NonPositiveReals and the excess constraint flips to delta <= Cost - eta; the objective expression and sense are unchanged. Replaces the Phase 1 NotImplementedError guard. Tests (extend existing, already wired into CI + run_coverage.bash): - test_cvar.py: maximize closed-form EF (lower-tail) + pure CVaR, a structural maximize check, and a serial PH-on-CVaR run asserting a valid (rho-independent) outer bound. - test_generic_cylinders.py: --EF --cvar matches a direct EF-CVaR build. - test_with_cylinders.py: PH hub + Lagrangian (outer) and + xhatshuffle (inner) bracket the EF-CVaR optimum. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- examples: farmer_generic.bash and run_all.py gain risk-averse (--cvar) invocations. - docs: new doc/src/risk_management.rst (registered in the toctree), CVaR flags in generic_cylinders.rst. The docs emphasize that the VaR variable eta has a much larger cost scale than typical model variables, so a uniform rho stalls PH; a cost-aware rho (--grad-rho or --sep-rho) is recommended. Verified on the farmer: with --default-rho 1 the gap sits at 40% after 100 iterations, while --grad-rho and --sep-rho converge to the EF-CVaR optimum. The example scripts use --grad-rho accordingly. The Risk Management section also carries the verbatim single-root-stage / not-time-consistent caveat and the zhat4xhat note. Design doc reconciled (maximize implemented, phases 2/3 combined, rho guidance recorded). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #751 +/- ##
==========================================
+ Coverage 74.59% 74.63% +0.03%
==========================================
Files 166 166
Lines 21532 21544 +12
==========================================
+ Hits 16062 16079 +17
+ Misses 5470 5465 -5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| ef_opt = self._ef_cvar_opt() | ||
| self.assertIsNotNone(wheel.BestOuterBound) | ||
| # outer (lower) bound for this minimization | ||
| self.assertLessEqual(wheel.BestOuterBound, ef_opt + 1e-4 * abs(ef_opt)) |
There was a problem hiding this comment.
Any reason for the additional fudge factor? I wouldn't mind 1e-10 or so, but 1e-4 seems like a bug.
There was a problem hiding this comment.
Good catch — no real reason, the relative fudge factor was overkill and could mask a genuinely wrong-side bound. Tightened to 1e-8 * abs(ef_opt) (just round-off absorption) in 120cbe8; both CVaR tests still pass.
| ef_opt = self._ef_cvar_opt() | ||
| self.assertIsNotNone(wheel.BestInnerBound) | ||
| # inner (upper) bound for this minimization | ||
| self.assertGreaterEqual(wheel.BestInnerBound, ef_opt - 1e-4 * abs(ef_opt)) |
There was a problem hiding this comment.
Same fix here — now 1e-8 * abs(ef_opt) in 120cbe8.
… review) The CVaR sandwich tests assert that the Lagrangian outer bound and the xhatshuffle inner bound sit on the correct side of the EF-CVaR optimum. These inequalities hold exactly up to solver round-off, so the slack should be a tiny numerical epsilon, not a 1e-4 relative tolerance (~22 units on this problem) that could mask a genuinely invalid bound. Per @bknueven's review, drop the relative fudge factor to 1e-8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bknueven
left a comment
There was a problem hiding this comment.
I would like to spend more time to understand this better, but that shouldn't hold it up, and I might not get said time for at least another week.
What
Combined Phase 2 + Phase 3 of CVaR risk management (follow-on to #746, which landed the core transform and EF tests). Per the design doc
doc/designs/cvar_design.md, this adds the command-line /generic_cylinderssurface, completes maximize support, and ships the user docs — so the risk-averse objectiveλ·E[Cost] + β·CVaR_α(Cost)is usable end-to-end across the EF solve and every cylinder.Phases 2 and 3 are combined into one PR at the maintainer's request. Opened as a draft.
CLI + decomposition (Phase 2)
cvar_args()adds--cvar,--cvar-weight(β),--cvar-alpha(α),--cvar-mean-weight(λ); wired intompisppy/generic/parsing.py.--cvaris set,scenario_creatoris wrapped withcvar_scenario_creator(after the ADMM block; guarded against bundles/ADMM, which it can't yet compose with). Becauseηis appended to the root node it is "just another first-stage variable," so EF, PH/APH, Lagrangian, subgradient, FWPH, and xhat all inherit risk aversion with no algorithm changes.examples/farmer/farmer_generic.bashandexamples/run_all.pygain risk-averse--cvarinvocations.Maximize support (Phase 3)
add_cvarnow handles maximization via the PySP lower-tail mirror: the excess variable becomesNonPositiveRealsand the excess constraint flips toδ_s ≤ Cost_s − η. The objective expression and sense are unchanged, so the same--cvarflags maximizeλ·E[Reward] + β·CVaR_αof the worst-case (lowest) rewards. This replaces the Phase 1NotImplementedErrorguard (which addressed @bknueven's review on #746).Setting rho with CVaR (important)
The VaR variable
ηhas a much larger cost scale than typical model variables (it lives on the objective scale), so a uniformrhostalls PH. Measured on the 3-scenario farmer (--cvar-weight 2 --cvar-alpha 0.8, EF-CVaR optimum-220700):--default-rho 1ηstuck)--grad-rho --grad-order-stat 0.5--sep-rhoThe new docs and example scripts recommend and use a cost-aware rho.
Docs (Phase 3)
New
doc/src/risk_management.rst(registered in the toctree): CLI + programmatic usage, the rho/ηguidance above, the maximize note, azhat4xhatconfidence-interval note (it evaluates whichever objective is active, so it automatically uses the risk-averse one), and the verbatim single-root-stage / not-time-consistent caveat from design §6.5. CVaR flags also added togeneric_cylinders.rst.Tests
Extended existing (already CI- and
run_coverage.bash-wired) files:test_cvar.py: maximize closed-form EF (lower-tail) + pure CVaR, a structural maximize check, and a serial PH-on-CVaR run asserting a valid (rho-independent) outer bound. The maximize closed form: rewards{10,20,30,40}uniform, α=0.6 ⇒ E=25, lower-VaR η*=20, lower-CVaR=13.75.test_generic_cylinders.py:--EF --cvarthrough the CLI matches a directly-built EF-CVaR objective.test_with_cylinders.py: PH hub + Lagrangian (outer) and + xhatshuffle (inner) bracket the EF-CVaR optimum — the rho-independent bound sandwich.All green locally (serial via pytest;
test_with_cylinders.pyviampiexec -np 2);ruffclean; docs build succeeds.🤖 Generated with Claude Code