netdes: add average_scenario_creator for Jensen's bound#674
Merged
Conversation
Refactor build_scenario_model into _scenario_data/_build_model underscore helpers per the §7 farmer pattern in doc/jensens_bound_design.md, then add average_scenario_creator that probability-weighted-averages d, u, b across all scenarios (netdes ships non-uniform probabilities, so this is not a simple arithmetic mean). Move all three netdes entries in run_all.py from the second-part block to the first part to keep part 2 fast, and add a third entry that smoke-tests the new code via --lagrangian-try-jensens-first --xhatshuffle-try-jensens-first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9494457 to
3991dde
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #674 +/- ##
==========================================
+ Coverage 70.93% 70.95% +0.02%
==========================================
Files 154 154
Lines 19252 19263 +11
==========================================
+ Hits 13657 13669 +12
+ Misses 5595 5594 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
The xhat-jensens code in each xhat spoke's main() was calling self.opt.evaluate(cache), which under the hood runs solve_loop(compute_val_at_nonant=True). That path unconditionally calls pyo.value(objfct) on every local scenario after the solve, which crashes when a solve produced no solution -- e.g. when the EV first-stage is infeasible for some real scenario in a model with no penalty/dummy variable. The regular xhat path (xhatbase._try_one) already handles this case: solve_loop with need_solution=False, then check no_incumbent_prob() and return None on infeasibility so the spoke can silently move on. Refactor that pattern into _JensensMixin._jensens_evaluate_xhat and call it from xhatshuffle, xhatxbar, xhatlooper, and xhatspecific main() in place of self.opt.evaluate. Restore the --xhatshuffle-try-jensens-first flag to the new netdes run_all entry so CI exercises the silent-skip path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The four xhat spokes had identical 6-line jensens blocks copy-pasted into their main() methods. Three of those copies are unreachable from CI -- only xhatshuffle had a run_all entry exercising it -- so codecov flagged them as uncovered patch lines. Pull the whole block (including the --*-try-jensens-first off short-circuit and the if-Eobj-is-not-None bound update) into one mixin helper, _try_average_scenario_xhat. Each xhat spoke now calls it on a single line. Unit-test the helper directly via stubbed self.opt: covers the disabled-flag, feasible-Eobj, and infeasible- None branches centrally instead of relying on four separate run_all integration runs. Co-Authored-By: Claude Opus 4.7 (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.
Summary
examples/netdes/netdes.pyalong the §7 farmer pattern fromdoc/jensens_bound_design.md: split the oldbuild_scenario_modelinto_scenario_data(thin wrapper overparse, returns dict) and_build_model(Pyomo).scenario_creatornow goes through both; external behavior unchanged.average_scenario_creator(scenario_name, path=None)that probability-weighted-averages the second-stage data (d,u,b) across all scenarios in the instance file. Netdes ships non-uniform probabilities so this is not a simple arithmetic mean. Returns a model with_mpisppy_probability=1.0.uandbare RHS-only (clean Jensen's case) butdis an objective coefficient (Q is concave ind), so users need to think before flipping--lagrangian-try-jensens-first. The xhat path is unconditionally fine.examples/run_all.py: move all three netdes entries from second part → first part since the second part is the long pole. Add a third netdes entry that smoke-tests the new code via--lagrangian-try-jensens-first --xhatshuffle-try-jensens-firstthrough the generic driver. Header comments updated for both parts;direct_solver_namecalculation moved with the OBBT-using entry.sslp is not included here — it has integer recourse plus a Binary-typed stochastic param (
ClientPresent), which needs a separate design discussion. Will be a follow-up PR.Test plan
ruff check examples/netdes/netdes.pypassesexamples/netdes/netdes.pysmoke:scenario_creator('Scen3', path=...)andaverage_scenario_creator('avg', path=...)both build; constraint counts match; average scenario has_mpisppy_probability=1.0network-10-10-L-01.dat(objective 80788.39)examples/run_all.pyparsespython examples/run_all.py <solver> --first-part-onlyrun (delegating to CI)