Skip to content

Add an unopy interface#483

Open
robfalck wants to merge 12 commits intomdolab:mainfrom
robfalck:unopy
Open

Add an unopy interface#483
robfalck wants to merge 12 commits intomdolab:mainfrom
robfalck:unopy

Conversation

@robfalck
Copy link
Copy Markdown
Contributor

Purpose

This pull request adds an interface to Uno to pyoptsparse.
Uno is a C++ package for nonlinear constrained optimization, with a python interface (unopy).
Uno provides another option that provides both SQP and interior-point capability depending on the settings.

Other notes about the Uno interface

  • Supports sparse jacobians
  • Supports user termination
  • Currently output is only sent to standard output, with verbosity set by the 'logger' option.

This implementation requires unopy 0.4.0 or later.

Expected time until merged

A few weeks (not urgent).

Type of change

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (non-backwards-compatible fix or feature)
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no API changes)
  • Documentation update
  • Maintenance update
  • Other (please describe)

Testing

Running the test suite will test Uno against several existing test problems.

Checklist

  • I have run ruff check and ruff format to make sure the Python code adheres to PEP-8 and is consistently formatted
  • I have formatted the Fortran code with fprettify or C/C++ code with clang-format as applicable
  • I have run unit and regression tests which pass locally with my changes
  • I have added new tests that prove my fix is effective or that my feature works
  • I have added necessary documentation

Resolves cvanaret/Uno#318

  Implements a pyoptsparse wrapper for the UNO (Unified Nonlinear Optimizer)
  using the unopy package. Follows the pyIPOPT pattern: COO sparse Jacobian
  format, constraint reordering via getOrdering, and MPI rank-0/waitLoop split.

  unopy callbacks receive x as unopy.Vector and output arrays as
  unopy.PointerToDouble, neither of which expose the buffer protocol.
  x is converted via np.fromiter using the callback's nv count parameter.
  Output arrays are mapped to writable numpy views via a two-level ctypes
  dereference through pybind11's simple_value_holder layout, avoiding
  per-element Python/C++ round-trips for bulk array writes.
@robfalck robfalck requested a review from marcomangano as a code owner March 20, 2026 01:13
@robfalck
Copy link
Copy Markdown
Contributor Author

@cvanaret I would appreciate your review again. Theres a skipped test of problem 109 (TP109) from the Schittkowski test that I wasn't able to get to converge with Uno.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.99%. Comparing base (5c36964) to head (85cd85c).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #483   +/-   ##
=======================================
  Coverage   82.99%   82.99%           
=======================================
  Files           1        1           
  Lines         147      147           
=======================================
  Hits          122      122           
  Misses         25       25           

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

@@ -205,11 +196,13 @@ def test_uno_informs(self):
optOptions={"time_limit": 1e-15, "logger": "INFO"})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is "time_limit": 1e-15 correct?

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.

That was a test to make sure the solution inform indicates that the time limit was reached.

@cvanaret
Copy link
Copy Markdown

@robfalck thanks for the PR!

I have an AMPL model of hs109 and Uno indeed doesn't converge with hessian_model="LBFGS" + the default quasi_newton_memory_size=6. It looks like the Hessian approximation is too positive definite and the solver takes tiny steps.
It does converge for quasi_newton_memory_size=15 but that's just lucky.

I think I should monitor progress and flush the limited memory if the solver stalls. Note that I'm also planning to implement interior points with L-BFGS in the next few days.

Will the exact Hessian callback be available in the near future?

@robfalck
Copy link
Copy Markdown
Contributor Author

Thanks for checking. That last question is up to the mdolab folks since it's a significant change.

@cvanaret
Copy link
Copy Markdown

cvanaret commented Mar 25, 2026

unopy v0.4.1 is out with:

  • L-BFGS for interior-point methods:
uno_solver.set_preset("ipopt")
# either set the Hessian model explicitly
uno_solver.set_option("hessian_model", "LBFGS")
# or let Uno default to L-BFGS if no Hessian is provided
  • a function set_logger_stream:
uno_solver.set_logger_stream(sys.stdout)
uno_solver.optimize(model)
sys.stdout.flush()

# or

with open("log.txt", "w") as f:
    uno_solver.set_logger_stream(f)
    uno_solver.optimize(model)
    f.flush()

@marcomangano
Copy link
Copy Markdown
Collaborator

Will the exact Hessian callback be available in the near future?

It would be a very cool capability to add since most gradient-based optimizers should support that, but I don't believe that the lab (pinging @A-CGray @eirikurj ) is interested in adding this capability in the near term. Most of the (high-fidelity) codes used for engineering MDAO do not provide second derivatives.

I haven't thought this through yet but I think it would require a change in API of the _masterFunc and _masterFunc2 callback function wrappers and some non-naive way to store sparsity information, so it would not be a trivial implementation. I also know that @ewu63 has been considering some major code refactoring at some point, so this change could fall under that bigger effort.

@A-CGray
Copy link
Copy Markdown
Member

A-CGray commented Mar 26, 2026

Will the exact Hessian callback be available in the near future?

It would be a very cool capability to add since most gradient-based optimizers should support that, but I don't believe that the lab (pinging @A-CGray @eirikurj ) is interested in adding this capability in the near term. Most of the (high-fidelity) codes used for engineering MDAO do not provide second derivatives.

I wouldn't complain if somebody else implemented it, but we have no plans to. We are rarely (if ever) solving problems where the Hessian is computable in any practical sense. If somebody was going to implement this, we may as well implement the ability to use jacobian-vector products as well.

Thanks for doing this work @robfalck , I have been meaning to try out Uno for a long time, now I might actually get around to it!

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.

Integrate Uno into pyOptSparse

4 participants