Skip to content

netdes: add average_scenario_creator for Jensen's bound#674

Merged
DLWoodruff merged 3 commits into
Pyomo:mainfrom
DLWoodruff:jensens_examples_netdes
May 3, 2026
Merged

netdes: add average_scenario_creator for Jensen's bound#674
DLWoodruff merged 3 commits into
Pyomo:mainfrom
DLWoodruff:jensens_examples_netdes

Conversation

@DLWoodruff
Copy link
Copy Markdown
Collaborator

@DLWoodruff DLWoodruff commented May 3, 2026

Summary

  • Refactor examples/netdes/netdes.py along the §7 farmer pattern from doc/jensens_bound_design.md: split the old build_scenario_model into _scenario_data (thin wrapper over parse, returns dict) and _build_model (Pyomo). scenario_creator now goes through both; external behavior unchanged.
  • Add 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.
  • Big docstring caveat on Jensen's-bound validity: u and b are RHS-only (clean Jensen's case) but d is an objective coefficient (Q is concave in d), 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-first through the generic driver. Header comments updated for both parts; direct_solver_name calculation 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.py passes
  • examples/netdes/netdes.py smoke: scenario_creator('Scen3', path=...) and average_scenario_creator('avg', path=...) both build; constraint counts match; average scenario has _mpisppy_probability=1.0
  • Average scenario LP solves with cplex on network-10-10-L-01.dat (objective 80788.39)
  • Full EF on the same instance solves to 88557.30 — average is below the EF optimum, consistent with Jensen's lower-bound theory for the convex (RHS-randomness) part of the recourse on this instance
  • examples/run_all.py parses
  • Full python examples/run_all.py <solver> --first-part-only run (delegating to CI)

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>
@DLWoodruff DLWoodruff force-pushed the jensens_examples_netdes branch from 9494457 to 3991dde Compare May 3, 2026 18:03
@codecov
Copy link
Copy Markdown

codecov Bot commented May 3, 2026

Codecov Report

❌ Patch coverage is 52.63158% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.95%. Comparing base (665e876) to head (3ff3a53).

Files with missing lines Patch % Lines
mpisppy/cylinders/xhatlooper_bounder.py 0.00% 3 Missing ⚠️
mpisppy/cylinders/xhatspecific_bounder.py 0.00% 3 Missing ⚠️
mpisppy/cylinders/xhatxbar_bounder.py 0.00% 3 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

DLWoodruff and others added 2 commits May 3, 2026 11:22
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>
@DLWoodruff DLWoodruff merged commit 7fa56fc into Pyomo:main May 3, 2026
28 checks passed
@DLWoodruff DLWoodruff deleted the jensens_examples_netdes branch May 3, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant