Skip to content

Helpful error when a solver can't handle the PH quadratic proximal term (issue #762, part 1)#768

Open
DLWoodruff wants to merge 2 commits into
Pyomo:mainfrom
DLWoodruff:prox-quadratic-solver-check
Open

Helpful error when a solver can't handle the PH quadratic proximal term (issue #762, part 1)#768
DLWoodruff wants to merge 2 commits into
Pyomo:mainfrom
DLWoodruff:prox-quadratic-solver-check

Conversation

@DLWoodruff

Copy link
Copy Markdown
Collaborator

Part 1 of issue #762

When Progressive Hedging uses a quadratic proximal term and the solver cannot handle a quadratic objective, the run fails confusingly. For HiGHS on a problem with integers/binaries (an MIQP), the failure is the cryptic status=unknown, TerminationCondition=unknown. This adds an actionable message.

Approach

PH's proximal term makes the subproblem objective quadratic unless --linearize-proximal-terms is set, so this is a quadratic-objective/solver mismatch. Pyomo's capability information is uneven (the legacy has_capability API reports it for solvers like cbc/glpk, but the APPSI and pyomo.contrib.solver wrappers used for HiGHS don't expose it), so a single up-front check isn't enough. Two complementary checks in phbase.iterk_loop cover both cases:

  • Proactive — before the first proximal solve, if the solver reports (via has_capability) that it cannot handle a quadratic objective, raise immediately with guidance. Catches e.g. cbc, glpk before wasting a solve.
  • Reactive — after the first proximal solve, if no subproblem anywhere produced a solution, raise with guidance. Covers solvers that expose no capability info (HiGHS et al.). The "no solution anywhere" test uses allreduce_or, so the raise decision is identical on every rank (no MPI desync).

Both checks no-op unless a non-linearized proximal term is actually active, and the reactive check is limited to iteration 1 — if the first quadratic solve succeeds, the solver supports quadratic objectives, so a later failure is a genuine optimization issue, not a capability problem. The error message points the user at --linearize-proximal-terms (and --linearize-binary-proximal-terms for binaries).

Changes

  • mpisppy/utils/sputils.py: solver_quadratic_objective_capability() — tri-state probe (True/False/None, where None means "unknown", never "unsupported").
  • mpisppy/phbase.py: _prox_is_quadratic, _check_prox_solver_capability (proactive), _check_prox_solve_succeeded (reactive); wired into iterk_loop.
  • mpisppy/tests/test_prox_solver_compat.py: solver-independent unit tests (probe tri-state, both guards, MPI-consistency via allreduce_or), wired into run_coverage.bash and .github/workflows/test_pr_and_main.yml.

Validation

  • New unit tests: 16 pass.
  • Existing PH tests (test_ef_ph.py): pass, with no false positives on a quadratic-capable solver (one unrelated pre-existing failure, test_scenario_lpwriter_extension, fails identically on main).
  • End-to-end: PH with glpk (no QP) at PHIterLimit=1 now raises Solver 'glpk' reports that it cannot handle a quadratic objective ... Re-run with --linearize-proximal-terms ...; the same run with --linearize-proximal-terms completes.

🤖 Generated with Claude Code

@DLWoodruff

DLWoodruff commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

@tvalenciaz this addresses part 1 of #762 — please review it when you get a chance.

@codecov

codecov Bot commented Jun 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.32%. Comparing base (cafb0b4) to head (4d79c7c).

Files with missing lines Patch % Lines
mpisppy/phbase.py 89.28% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #768   +/-   ##
=======================================
  Coverage   74.31%   74.32%           
=======================================
  Files         166      166           
  Lines       21442    21477   +35     
=======================================
+ Hits        15935    15962   +27     
- Misses       5507     5515    +8     

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

@tvalenciaz

Copy link
Copy Markdown
Collaborator

This looks good to me! I tried it on the instances that prompted the issue submission and it works well.
However, for glpk and cbc, I tried running it and it seems to fail before it gets to the iteration 1 check. I am getting the message below from both cbc and glpk when running farmer (using the farmer_generic.bash script in the examples directory).

I think during Iter0(), the quadratic term is turned off by being multiplied by 0, but it's still there, which both solvers complain about. But this message is not as cryptic as the other one, so maybe acceptable?

pyomo.common.errors.InvalidExpressionError: Model objective (Total_Cost_Objective) contains nonlinear terms that cannot be written to LP format

@bknueven

Copy link
Copy Markdown
Collaborator

I think during Iter0(), the quadratic term is turned off by being multiplied by 0, but it's still there, which both solvers complain about.

@DLWoodruff I thought we attach W and prox after iteration 0? If not, maybe we should just do that so the problem we solve at iteration 0 is effectively what the user passes us. This would be good for debugging, generally.

DLWoodruff and others added 2 commits June 25, 2026 16:21
…issue Pyomo#762)

Progressive Hedging's proximal term makes the subproblem objective
quadratic (unless --linearize-proximal-terms is used). A solver that
cannot handle a quadratic objective then fails -- and for HiGHS on an
MIQP the failure is cryptic (TerminationCondition=unknown). Detect this
and tell the user what to do, via two complementary checks in iterk_loop:

- Proactive: before the first proximal solve, if the solver reports via
  the legacy has_capability API that it cannot handle a quadratic
  objective (e.g. cbc, glpk), raise immediately with guidance.
- Reactive: after the first proximal solve, if no subproblem anywhere
  produced a solution, raise with guidance. This covers solvers that do
  not expose has_capability (e.g. HiGHS via the APPSI / contrib.solver
  interfaces). The "no solution anywhere" test uses allreduce_or so the
  raise decision is identical on every rank.

Both checks no-op unless a non-linearized proximal term is actually
active, and the reactive check is limited to iteration 1 (if the first
quadratic solve works, the solver supports quadratic objectives).

Adds sputils.solver_quadratic_objective_capability (tri-state probe) and
a solver-independent unit test, wired into run_coverage.bash and the CI
workflow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(issue Pyomo#762)

cbc and glpk reject a quadratic objective by *raising* in the LP writer
during the first proximal solve -- before the reactive "no solution
anywhere" check can run -- and the proactive has_capability probe does not
catch them in every Pyomo version / solver interface. Wrap the iter-1
solve so that a raise there is re-raised with the actionable
--linearize-proximal-terms message (chaining the original exception so the
real traceback is preserved). Factor the shared remediation text into a
module-level _LINEARIZE_PROX_HINT used by all three checks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DLWoodruff DLWoodruff force-pushed the prox-quadratic-solver-check branch from 4647505 to 4d79c7c Compare June 25, 2026 23:22
@DLWoodruff

Copy link
Copy Markdown
Collaborator Author

The PR now addresses Tomas's issues with cbc and glpk (although we wonder if the latest Pyomo was running).
Ben's suggestion about when to attach W needs discussion (BTW: Pyomo removes terms that it can evaluation to zero so quadratic terms were not making it through to the solver on iteration 0; it was iteration 1, but I think that is not fixed with a proper error message)

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.

3 participants