Skip to content

Multistage feasible_xhat_creator (engine + aircond example)#766

Draft
DLWoodruff wants to merge 2 commits into
Pyomo:mainfrom
DLWoodruff:multistage-feasible-xhat
Draft

Multistage feasible_xhat_creator (engine + aircond example)#766
DLWoodruff wants to merge 2 commits into
Pyomo:mainfrom
DLWoodruff:multistage-feasible-xhat

Conversation

@DLWoodruff

Copy link
Copy Markdown
Collaborator

Summary

Extends the feasible_xhat_creator convention (see doc/src/feasible_xhat.rst)
to multistage problems. A model author can supply a candidate incumbent that
is feasible to fix in every real scenario's per-scenario subproblem; an xhat spoke
pins and evaluates it once before its main loop, via
--<xhatter>-try-feasible-xhat-first. Until now this was two-stage only.

The headline: the in-cylinder mechanism was already multistage. _fix_nonants
loops over every node of the scenario tree, and _try_feasible_xhat only validates
"ROOT" in cache, so a multistage model returning a full-tree cache already works
with no spoke or framework changes. "Two-stage only" was a property of the
engines and docs, not the machinery. So this PR is small.

Design doc: doc/designs/multistage_feasible_xhat_design.md.

What's included

  • mpisppy/utils/xhat_helpers.py::ef_xhat_nonants — the multistage engine:
    solves one extensive form over a (proxy) scenario set and returns
    {node_name: np.ndarray} over all non-leaf nodes, each array ordered to match
    _fix_nonants' per-node loop (built from sputils.create_EF / ref_vars). The
    shared solve is refactored out of _solve_and_extract_root into _solve_model.
    The two-stage engines (average_xhat_nonants, lp_xbar_nonants) are unchanged.
  • mpisppy/tests/examples/aircond_auxiliary.py — a worked multistage example.
    Its feasible_xhat_creator solves the expected-value tree (sigma_dev=0):
    same branching structure, deterministic mean demand. aircond has relatively
    complete recourse (the free Inventory variable absorbs any demand imbalance as
    penalized backorder via material balance; the only hard constraint on the node
    decisions is RegularProd <= Capacity), so any capacity-respecting plan is
    feasible to fix on every path — no rounding needed, the multistage analogue
    of farmer. Placed beside the model (mpisppy.tests.examples.aircond) so the
    <module>_auxiliary discovery resolves.
  • doc/src/feasible_xhat.rst — a "Multistage" section covering the one
    genuinely new idea, inter-stage feasibility coupling: the node vectors are
    coupled along each path, so you cannot assemble a candidate from independent
    per-node choices; derive them all from one feasible solve of a proxy tree with
    the same node structure. Includes the repair note — relatively complete recourse
    needs none; integer/tightly-coupled recourse needs a model-specific path-feasible
    repair, which mpi-sppy does not ship (the model author's responsibility).
  • mpisppy/tests/test_feasible_xhat.pyTestAircondFeasibleXhatCreator:
    builds the expected-value cache (branching [3,3], 9 scenarios, non-leaf nodes
    ROOT/ROOT_0/ROOT_1/ROOT_2), then pins it on every real stochastic
    scenario and asserts each stays feasible. The test file is already wired into
    run_coverage.bash and test_pr_and_main.yml.

Scope / non-goals

  • Not "read the xhat from a file" — that's a separate input mechanism
    (--xhat-from-file, Multi-stage xhat file: read and write (by-name CSV) #760). This example constructs the candidate in memory.
  • No automatic multistage repair for integer/coupled-recourse models (flagged in
    docs as the model author's job).
  • The two-stage engines stay two-stage; ef_xhat_nonants is additive.

Testing

  • test_feasible_xhat.py: 28/28 pass (3 new aircond + 25 existing).
  • ruff check clean; docs build with no warnings on feasible_xhat.

🤖 Generated with Claude Code

DLWoodruff and others added 2 commits June 17, 2026 16:00
The in-cylinder mechanism is already multistage (_fix_nonants loops all
nodes; _try_feasible_xhat only checks ROOT), so this needs no spoke or
framework changes. New work is one engine (ef_xhat_nonants), one example
(aircond_auxiliary, complete-recourse so no rounding), a doc revision,
and a test. Captures the one genuinely new idea — inter-stage
feasibility coupling: derive all node vectors from one coherent proxy
solve, not independent per-node points. Construction-not-from-a-file by
design; the CSV xhat-file path stays a separate input mechanism.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nd example)

The in-cylinder mechanism was already multistage (_fix_nonants loops all
nodes; _try_feasible_xhat only checks ROOT), so no spoke or framework
code changes. New:

- ef_xhat_nonants in xhat_helpers.py: solves one EF over a (proxy)
  scenario set and returns {node_name: ndarray} over all non-leaf nodes,
  ordered to match _fix_nonants. Reuses create_EF/ref_vars. (Refactors
  the shared solve out of _solve_and_extract_root into _solve_model.)
- mpisppy/tests/examples/aircond_auxiliary.py: feasible_xhat_creator
  using the expected-value tree (sigma_dev=0). aircond has relatively
  complete recourse (Inventory absorbs imbalance as penalized
  backorder), so any capacity-respecting plan is feasible to fix on
  every path -- no rounding, the multistage analogue of farmer. Placed
  beside the model so <module>_auxiliary discovery resolves.
- doc/src/feasible_xhat.rst: Multistage section -- the inter-stage
  coupling principle (derive all node vectors from one coherent solve,
  not independent per-node points) and the repair note (complete
  recourse needs none; integer/coupled recourse needs a model-specific
  path-feasible repair, not shipped).
- test_feasible_xhat.py: aircond end-to-end (build EV cache, pin on all
  9 real stochastic scenarios, assert feasible). Already wired into the
  coverage harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 74.13%. Comparing base (efd19a4) to head (bd75969).

Files with missing lines Patch % Lines
mpisppy/utils/xhat_helpers.py 92.30% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #766   +/-   ##
=======================================
  Coverage   74.13%   74.13%           
=======================================
  Files         166      166           
  Lines       21181    21193   +12     
=======================================
+ Hits        15703    15712    +9     
- Misses       5478     5481    +3     

☔ View full report in Codecov by Harness.
📢 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.

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