Skip to content

Fix outer/inner bound assignment for maximization in amalgamator - #837

Open
DLWoodruff wants to merge 1 commit into
Pyomo:mainfrom
DLWoodruff:fix-amalgamator-max-bounds
Open

Fix outer/inner bound assignment for maximization in amalgamator#837
DLWoodruff wants to merge 1 commit into
Pyomo:mainfrom
DLWoodruff:fix-amalgamator-max-bounds

Conversation

@DLWoodruff

Copy link
Copy Markdown
Collaborator

Fixes #836.

In Amalgamator.run, the best_outer_bound / best_inner_bound assignment branched on is_minimizing, but both branches assigned the same right-hand sides — only the order of the two statements differed, which has no effect. For a maximization model the solver's Upper bound is the relaxation (outer) bound and the Lower bound is the incumbent (inner) bound, so the else branch had them backwards.

The fix mirrors the handling already in mpisppy/spopt.py (lines 370-373 and 432-437), which branches on the sense correctly. Minimization is unaffected, since the if branch was already right.

Why it matters

ciutils.gap_estimators takes the batch optimum as ama_object.best_outer_bound. On a maximization model that was the incumbent rather than the relaxation bound, so the batch gap lost the conservative direction it is meant to have; that feeds the MMW confidence interval in mmw_ci.py and the sequential-sampling drivers.

Scope and testing

Deliberately minimal: two right-hand sides, plus a comment on why the mapping depends on the sense.

I have not added a regression test. The existing amalgamator tests all require a solver and a full EF solve, so covering this would mean adding a maximization example model and gating the test on solver availability — disproportionate to a two-line fix, and a maintainer call rather than mine. If you would like the coverage, the cheaper route is the one the #TBD : Write a function doing this comment already suggests: extract the sense-to-bound mapping into a small helper shared with spopt.py, which would make it unit-testable with no solver at all. Happy to do that in a follow-up if you want it.

Verified that ruff check mpisppy/utils/amalgamator.py reports the same 9 pre-existing findings before and after, so the change is lint-neutral.

🤖 Generated with Claude Code

The assignment branched on is_minimizing, but both branches assigned the
same values; only the order of the two statements differed. For a
maximization model the solver's Upper bound is the relaxation (outer)
bound and the Lower bound is the incumbent (inner) bound, so the else
branch had them backwards.

This mirrors the handling already in spopt.py, which branches correctly.
Minimization is unaffected.

Fixes Pyomo#836

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.78%. Comparing base (29671ec) to head (350eb90).

Files with missing lines Patch % Lines
mpisppy/utils/amalgamator.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #837   +/-   ##
=======================================
  Coverage   77.77%   77.78%           
=======================================
  Files         177      177           
  Lines       23764    23764           
=======================================
+ Hits        18482    18484    +2     
+ Misses       5282     5280    -2     

☔ 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.

amalgamator: best_outer_bound and best_inner_bound are not swapped for maximization

1 participant