Skip to content

Scipy interface#705

Open
FrancoisGallard wants to merge 36 commits into
cvanaret:mainfrom
FrancoisGallard:scipy_hess
Open

Scipy interface#705
FrancoisGallard wants to merge 36 commits into
cvanaret:mainfrom
FrancoisGallard:scipy_hess

Conversation

@FrancoisGallard

@FrancoisGallard FrancoisGallard commented May 10, 2026

Copy link
Copy Markdown

This is a proposal for a scipy-like interface of UNO.

It works with unopy 0.2.0 under linux. Porting to the latest unopy version needs to be done @cvanaret help is welcome on this.
Since unopy supports array asignation ( vectyor[:] = myarray) this needs to be used once ported.

I first implemented it manually, and stuggled with the Hessian lagrangian. I asked @jgiret for help, we used Claude code, I provided the unit tests and the specification (method comments and signature), the agent looped until tests worked. I manually reviewed the code and did some minor changes.

It could be further reviewed.

One test (funnelsqp on constrained problem) is non deterministic on my machine (still unopy v0.2.0), the status is "solver error", sometimes it passes, so I think it is not an issue in the interface but rather in the C code, that has maybe been fixed in the latest version.

@FrancoisGallard

Copy link
Copy Markdown
Author

@cvanaret We are going to ask Claude to update the code for unopy 0.4.0 with @jgiret , we ll keep you updated

@cvanaret

Copy link
Copy Markdown
Owner

@FrancoisGallard I think the only breaking change is that the Lagrangian convention should be set with set_lagrangian_sign_convention. Happy to make the changes myself.
I'll add CI workflows to run your tests and I'll upgrade to unopy 0.4.*

@cvanaret

cvanaret commented May 13, 2026

Copy link
Copy Markdown
Owner

@FrancoisGallard
Turns out I broke more stuff than I thought between unopy 0.3.* and 0.4.* 😅
In the end if was a bit of a trial and error to get pytest running but it's finally working. Now we have to understand why some instances fail.
Note: for some reason no Hessian operator is detected, so Uno defaults to L-BFGS. Ideally, we should pass the explicit Hessian as well (which, looking at the code of hessian_operator_callback, shouldn't be too difficult).

You can have a look at the Scipy interface to clean it up, I probably forgot some np.array -> list conversions.

Edit: ah, I wouldn't test the SLP preset. It converges very slowly and there's few reasons to use it.

@cvanaret

Copy link
Copy Markdown
Owner

The Scipy tests pass ✌️

@FrancoisGallard

Copy link
Copy Markdown
Author

Well done, thanks Charlie ! I'll have give it a try

@cvanaret

cvanaret commented May 13, 2026

Copy link
Copy Markdown
Owner

The GEMSEO tests pass because we only look at the solution, but the funnelsqp run fails with Algorithmic error even though all residuals are small. Can't explain that one. Works fine on my machine.

@FrancoisGallard

Copy link
Copy Markdown
Author

I tried under windows and unopy 0.4.7, it works well, thanks a lot ! IPOPt works too. I have the same numerical issue as you.

@cvanaret

cvanaret commented May 14, 2026

Copy link
Copy Markdown
Owner

@FrancoisGallard
there's something fishy going on with the Power2 test. The objective gradient is $g(x) = 2x$ with $x \in \mathbb{R}^3$, and when I perform debug calls to $g((1, 1, 1))$ I get:

  • $(2, 2, 2)$ (as expected) in test_gemseo_uno.test_power2()
  • $(4, 4, 4)$ in gemseo_uno._run()

Same issue with the constraint Jacobian (I get some 6 instead of some 3).

Is there a function somewhere that preprocesses/modifies the problem after the call to execute() and before the call to _run()?

@FrancoisGallard

FrancoisGallard commented May 15, 2026

Copy link
Copy Markdown
Author

@FrancoisGallard there's something fishy going on with the Power2 test. The objective gradient is g ( x ) = 2 x with x ∈ R 3 , and when I perform debug calls to g ( ( 1 , 1 , 1 ) ) I get:
Same issue with the constraint Jacobian (I get some 6 instead of some 3).

Is there a function somewhere that preprocesses/modifies the problem after the call to execute() and before the call to _run()?

Yes, by default GEMSEO scales the design variables and the solver sees all bounds as [0, 1]. This can be deactivated with the solver opitons:
settings_model=UNO_Settings(xtol_rel=1e-4, max_iter=50, normalize_design_space=False)

settings_ = self._filter_settings(self._settings.model_dump(), UNO_Settings)

# Deactivate stopping criteria which are handled by GEMSEO
tolerance = 0.0

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This sets the primal-dual tolerances to 0, therefore Uno most likely won't terminate (see Power2 test).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

GEMSEO has its own stop criteria based on xtol, ftol, maxiter or KKT, so we always deactivate the solver tolerances to have consistent stopping criteria when the user changes of optimizer.

@cvanaret cvanaret May 15, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

But how do you expect the optimizer to terminate if you set the tolerance to 0? Surely you usually pass a termination callback of some sort? It's not the case for Uno (yet).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It does not by itself, we raise an exception in the objective function call and that stops the optimization, then we catch it and handle the search for the best point. We can also add an option to activate the solver stopping criteria if you want to check that all goes well.

@cvanaret cvanaret May 15, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Ooh that's a brittle strategy. That works for (say) Fortran codes because they cannot catch exceptions, but you cannot assume any solver will behave the way you expect. Uno always catches the evaluation exceptions and backtracks. In the Power2 case, I can read from the Uno log: A function could not be evaluated. The trust-region radius will be reduced: indeed the radius is decreased 30 times in a row until Uno terminates with Algorithmic error.

A good alternative would be to keep the tolerances to 0 and set a user termination callback based on GEMSEO criteria. It's implemented in the C API (see here, and used here in the Uno outer loop), so it would be minimal effort to add this to unopy.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I think ensuring that we catch the exception in pyuno and manage the solver stop there would be better. It will appear in many user scripts independently of gemseo that exceptions be raised when calling the objective.

@FrancoisGallard FrancoisGallard May 19, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@cvanaret if you can expose to unopy the uno_termination_callback from the c API, I will try to make this work.

@cvanaret cvanaret May 19, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

OK I'm on it: #720

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

If you are OK I propose to add a try catch around the evaluation of the functions in the scipy interface, if an exception is catched, we return a NaN and then store this state of error in a global variable, and the termination callback returns True based on this global state.

@cvanaret cvanaret May 20, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Let's give it a shot and we'll see what it looks like.

@cvanaret

Copy link
Copy Markdown
Owner

@FrancoisGallard got it. The bug is identified (see review above).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants