Skip to content

Fix non-rigorous crossed bound results#3948

Open
bernalde wants to merge 3 commits into
Pyomo:mainfrom
SECQUOIA:fix/issue-3947-nonrigorous-bounds
Open

Fix non-rigorous crossed bound results#3948
bernalde wants to merge 3 commits into
Pyomo:mainfrom
SECQUOIA:fix/issue-3947-nonrigorous-bounds

Conversation

@bernalde
Copy link
Copy Markdown
Contributor

Summary

  • Add crossed-bound result handling so non-certified crossed bounds return feasible and report no certified dual bound instead of manufacturing optimal with equal bounds.
  • Detect LOA models that may have non-rigorous dual bounds, including non-polynomial expressions and basic nonconvex quadratic structure, while preserving certified behavior for GLOA and convex quadratic LOA cases.
  • Apply the same non-certified crossed-bound result semantics to MindtPy GOA.

Tests run

  • pytest -q pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPoptUnit::test_loa_crossed_bounds_are_not_reported_as_global_optimal pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPoptUnit::test_loa_nonpolynomial_model_may_have_nonrigorous_dual_bound pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPoptUnit::test_loa_distinguishes_basic_quadratic_convexity pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPoptUnit::test_gloa_crossed_bounds_preserve_certified_optimal_behavior pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py::TestMindtPyGOAResults::test_goa_crossed_bounds_are_not_reported_as_global_optimal pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPopt::test_logical_constraints_on_disjuncts_nonlinear_convex pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPopt::test_reverse_numeric_differentiation_in_LOA -> 7 passed
  • pytest -q pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPoptUnit pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py -> 42 passed, 2 skipped
  • pytest -q pyomo/contrib/gdpopt/tests/test_gdpopt.py -> 64 passed, 19 skipped, 3 deselected
  • pytest -q pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py -> 29 passed
  • black --check --diff pyomo/contrib/gdpopt/algorithm_base_class.py pyomo/contrib/gdpopt/loa.py pyomo/contrib/gdpopt/tests/test_gdpopt.py pyomo/contrib/mindtpy/algorithm_base_class.py pyomo/contrib/mindtpy/global_outer_approximation.py pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py -> passed; Black emitted its Python 3.13 vs 3.14 parser warning and reported all six files unchanged

Notes

  • Full project pytest, docs, and typos workflows were not run locally.
  • A combined local invocation of pytest -q pyomo/contrib/gdpopt/tests/test_gdpopt.py pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py hit local MC++ shared-library loading failures (GLIBCXX_3.4.32 missing from the active libstdc++ for /home/bernalde/.pyomo/lib/mcppInterface.so); the same files passed when run separately as listed above.

Closes #3947

Copy link
Copy Markdown
Contributor Author

@bernalde bernalde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issue found in the LOA quadratic convexity classification. GitHub does not allow me to request changes on my own PR, so this is submitted as COMMENT, but I would not merge this until the blocking inline issue is addressed. Focused result-path tests pass locally; the broader changed-file pytest run is blocked by a local MCPP/libstdc++ loader mismatch in existing solver tests.

Comment thread pyomo/contrib/gdpopt/loa.py Outdated
repn = generate_standard_repn(expr, quadratic=True)
curvature = 0
for coef, (v1, v2) in zip(repn.quadratic_coefs, repn.quadratic_vars):
if v1 is not v2:
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: This treats every quadratic cross term as nonconvex/uncertified, but PSD/NSD quadratic forms can have off-diagonal terms. For example, x**2 + x*y + y**2 <= 4 is convex, and Pyomo already has GDP tests/model factories for that case in pyomo/gdp/tests/models.py and pyomo/gdp/tests/test_hull.py. With this detector, LOA marks those convex quadratic models as nonrigorous_dual_bound_possible, so any crossed-within-tolerance convergence returns feasible with an unbounded dual side instead of the certified optimal result users currently get. Please replace this diagonal-only sign check with a real PSD/NSD classification for quadratic forms, similar to the eigenvalue-based Q-matrix check used by the hull quadratic code, and add a regression test for a convex quadratic with an off-diagonal term.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 3fa0b4b. _quadratic_curvature() now builds the symmetric Q matrix and classifies PSD/NSD forms by eigenvalues, so convex off-diagonal terms are certified. Added test_loa_certifies_psd_quadratic_cross_terms for x**2 + x*y + y**2 <= 4 in pyomo/contrib/gdpopt/tests/test_gdpopt.py.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in ce6b6d6. LOA now builds a symmetric Q matrix and classifies PSD/NSD forms by eigenvalues, including the no-NumPy fallback path. The regression coverage is in test_loa_certifies_psd_quadratic_cross_terms_without_numpy.

@bernalde
Copy link
Copy Markdown
Contributor Author

Addressed the open review thread.

Commits pushed:

Main changes:

  • LOA now builds the symmetric quadratic Q matrix and uses eigenvalues to classify PSD/NSD forms, so convex cross terms such as x**2 + x*y + y**2 <= 4 are certified instead of treated as nonrigorous.
  • Added test_loa_certifies_psd_quadratic_cross_terms to preserve that behavior.

Tests run:

  • pytest -q pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPoptUnit: 14 passed, 2 skipped.
  • pytest -q pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py::TestMindtPyGOAResults: 1 passed.
  • python -m pytest -q pyomo/contrib/gdpopt/tests/test_gdpopt.py pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py: 7 failed, 87 passed, 19 skipped, 3 deselected. All failures are the existing local MCPP loader error: GLIBCXX_3.4.32 not found for /home/bernalde/.pyomo/lib/mcppInterface.so.
  • black --check --diff pyomo/contrib/gdpopt/algorithm_base_class.py pyomo/contrib/gdpopt/loa.py pyomo/contrib/gdpopt/tests/test_gdpopt.py pyomo/contrib/mindtpy/algorithm_base_class.py pyomo/contrib/mindtpy/global_outer_approximation.py pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py: passed; 6 files unchanged.
  • Ad hoc classifier check for x**2 + x*y + y**2 <= 4: returned False for _problem_may_have_nonrigorous_dual_bound.

Comments intentionally not addressed:

  • None.

Remaining risks or follow-up:

  • The broader local changed-file test run is still blocked by the local mcppInterface.so / GLIBCXX_3.4.32 environment mismatch. CI should provide the authoritative full solver-test result.

Copy link
Copy Markdown
Contributor Author

@bernalde bernalde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found one blocking issue. GitHub does not allow me to request changes on my own PR, so this is submitted as COMMENT, but I would not merge this until the blocking inline issue is addressed.

Comment thread pyomo/contrib/gdpopt/loa.py Outdated
if not repn.quadratic_coefs:
return 0

if numpy_available:
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: This makes certification of convex quadratic cross terms depend on optional NumPy. In the linux/3.10/slim CI job, NumPy is not installed, so this branch is skipped, _quadratic_curvature() falls through to the diagonal-only fallback, and test_loa_certifies_psd_quadratic_cross_terms fails because x**2 + x*y + y**2 <= 4 is still classified as potentially nonrigorous. This leaves the reviewed regression fixed only in optional-dependency environments. Please either implement the PSD/NSD check without requiring NumPy, or explicitly gate both the behavior and the test on NumPy availability; the former better preserves LOA's existing minimal-dependency behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in ce6b6d6. The diagonal-only fallback was replaced with a pure-Python Jacobi eigenvalue fallback when NumPy is unavailable, and the new no-NumPy test covers both a PSD cross-term form and an indefinite bilinear form.

@bernalde
Copy link
Copy Markdown
Contributor Author

Pushed ce6b6d63c (Support LOA quadratic certification without NumPy (#3947)).

Main changes:

  • Replaced the LOA diagonal-only curvature fallback with symmetric quadratic-matrix PSD/NSD classification.
  • Kept NumPy as the fast eigenvalue path when available and added a pure-Python Jacobi eigenvalue fallback when NumPy is unavailable.
  • Added a no-NumPy regression test that certifies a PSD quadratic with a cross term and still rejects an indefinite bilinear form.

Tests run:

  • pytest -q pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPoptUnit -> 15 passed, 2 skipped in 1.16s
  • pytest -q pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py::TestMindtPyGOAResults -> 1 passed in 0.38s
  • black --check --diff pyomo/contrib/gdpopt/algorithm_base_class.py pyomo/contrib/gdpopt/loa.py pyomo/contrib/gdpopt/tests/test_gdpopt.py pyomo/contrib/mindtpy/algorithm_base_class.py pyomo/contrib/mindtpy/global_outer_approximation.py pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py -> passed (6 files would be left unchanged)
  • Pure-Python classifier smoke checks confirmed the PSD cross-term model is certified with numpy_available = False, and sampled Jacobi eigenvalues matched NumPy.

Broader local check:

  • python -m pytest -q pyomo/contrib/gdpopt/tests/test_gdpopt.py pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py -> 7 failed, 88 passed, 19 skipped, 3 deselected in 37.00s
  • The failures are local solver-extension load failures from /home/bernalde/.pyomo/lib/mcppInterface.so: GLIBCXX_3.4.32 not found. They do not appear caused by this PR change.

Comments intentionally not addressed:

  • None. The prior top-level summary comment was informational.

Remaining risks / follow-up:

  • CI for the latest commit is still pending; the slim/minimal checks should confirm the no-NumPy path in the target environment.

Copy link
Copy Markdown
Contributor Author

@bernalde bernalde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues: none found.

Nonblocking issues: none.

Questions: none.

Tests run and outcomes:

  • PYTHONDONTWRITEBYTECODE=1 python -m pytest -q -p no:cacheprovider pyomo/contrib/gdpopt/tests/test_gdpopt.py::TestGDPoptUnit pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py::TestMindtPyGOAResults -> 16 passed, 2 skipped in 1.10s
  • PYTHONDONTWRITEBYTECODE=1 python -m pytest -q -p no:cacheprovider pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py -> 29 passed in 1.51s
  • PYTHONDONTWRITEBYTECODE=1 python -m pytest -q -p no:cacheprovider pyomo/contrib/gdpopt/tests/test_gdpopt.py -> 7 failed, 59 passed, 19 skipped, 3 deselected in 35.04s; all failures are local mcppInterface.so loader failures from GLIBCXX_3.4.32 not found, matching the existing local environment issue and not this PR's changed behavior
  • black --check --diff pyomo/contrib/gdpopt/algorithm_base_class.py pyomo/contrib/gdpopt/loa.py pyomo/contrib/gdpopt/tests/test_gdpopt.py pyomo/contrib/mindtpy/algorithm_base_class.py pyomo/contrib/mindtpy/global_outer_approximation.py pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py -> passed, 6 files unchanged
  • Additional no-NumPy smoke check: PSD cross-term classified certified, indefinite bilinear classified nonrigorous, mutable-Param PSD case classified certified

CI evidence inspected:

  • linux/3.10/slim, linux/3.10/bare-env, linux/3.12/numpy2, lint/style-and-typos, docs, and several macOS/Linux jobs passed.
  • The failed full Linux and win/3.10/pip jobs fail in GAMS solver tests with GAMS returncode=7, not in GDPopt or MindtPy. Jenkins and several matrix jobs are still pending.

I would not merge as-is until the required CI/Jenkins state is resolved, but I did not find PR-code changes that need to be addressed before merge.

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.

GDPopt LOA reports non-rigorous nonconvex bounds as optimal LB=UB

1 participant