-
-
Notifications
You must be signed in to change notification settings - Fork 52
Scipy interface #705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
FrancoisGallard
wants to merge
36
commits into
cvanaret:main
Choose a base branch
from
FrancoisGallard:scipy_hess
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Scipy interface #705
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
1ee45a5
First version of scipy interface and tests
FrancoisGallard 60004c1
Minor improvements on messages, status, unconstrained and BC tests
FrancoisGallard 76d0d2b
Apply Black formatter
FrancoisGallard e4a0265
Interface works with constraints
FrancoisGallard b35cd5f
Lincenses and doc
FrancoisGallard 88697c9
Improved scipy and GEMSO interfaces
FrancoisGallard c82b0a6
First implementaiton of Hesssian : still buggy
FrancoisGallard 0576a0d
Update with JC Giret and Claude code, works with Hessian, 1 test fails
FrancoisGallard c845d44
Minor cleanup
FrancoisGallard bb5d451
Added CI workflows
cvanaret 12251a0
Fixed paths
cvanaret 0932a56
Install pytest
cvanaret e2214b3
Install scipy in GEMSEO workflow
cvanaret 3738966
pip install pytest gemseo scipy
cvanaret 4f45285
Moved test files back into their respective directories + fixed paths
cvanaret 3e5d599
Run tests from root directory
cvanaret 8cb3c39
Tried to fix the unopy packaging
cvanaret 5db4a0a
python -m pytest --import-mode=importlib
cvanaret 9eec59b
Use model.set_lagrangian_sign_convention
cvanaret 37d5086
Upgrade to unopy 0.4.*
cvanaret 7add569
Added scipy_interface to PYTHONPATH
cvanaret 19ad406
Install scipy and GEMSEO interfaces
cvanaret 0b6913d
Disabled filterslp tests, enabled ipopt instead
cvanaret a5d2a2a
Added debug prints
cvanaret 2bfead1
Commented out test_rosen_constr
cvanaret ab56a84
Added debug print to understand Dimension mismatch in constraints_low…
cvanaret b97aff9
Fixed bounds length in test_rosen_constr2
cvanaret e896830
Custom exception for Jacobian evaluation error
cvanaret 74b53a8
Reverted to original constraint_jacobian_callback
cvanaret 60701e2
Removed ipopt test (Hessian operator not compatible with EQPSolver)
cvanaret 262e91e
Uncommented test_rosen_constr
cvanaret 2628245
Removed UNO_IPOPT from GEMSEO tests
cvanaret 906f48c
Removed prints
cvanaret 7174b20
Set logger to DEBUG3 to debug GEMSEO tests
cvanaret 80501f5
Allow pytest to print to stdout
cvanaret 25952f2
Logger: print subproblem and solution
cvanaret File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| name: Test unopy via GEMSEO interface | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ "main" ] | ||
| paths-ignore: | ||
| - '*.md' | ||
| - 'LICENSE' | ||
| - '*.cff' | ||
| - '*.yml' | ||
| - '*.yaml' | ||
| - 'docs/**' | ||
| pull_request: | ||
| branches: [ "main" ] | ||
| paths-ignore: | ||
| - '*.md' | ||
| - 'LICENSE' | ||
| - '*.cff' | ||
| - '*.yml' | ||
| - '*.yaml' | ||
| - 'docs/**' | ||
|
|
||
| env: | ||
| BUILD_TYPE: Debug | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| matrix: | ||
| os: [ubuntu-latest] | ||
| architecture: [x64] | ||
| python-version: ["3.12"] | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Python | ||
| # This is the version of the action for setting up Python, not the Python version. | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| # Semantic version range syntax or exact version of a Python version | ||
| python-version: ${{ matrix.python-version }} | ||
| architecture: ${{ matrix.architecture }} | ||
|
|
||
| - name: Install pybind11 | ||
| run: sudo apt install -y python3-pybind11 pybind11-dev | ||
|
|
||
| - name: Install pytest, gemseo, and scipy | ||
| run: pip install pytest gemseo scipy | ||
|
|
||
| - name: Download dependencies | ||
| run: bash dependencies/scripts/download_dependencies.sh | ||
|
|
||
| - name: Compile and install unopy | ||
| run: CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) pip install . -v | ||
|
|
||
| - name: Run tests | ||
| run: python -m pytest -s --import-mode=importlib ${{github.workspace}}/interfaces/Python/gemseo_uno/test_gemseo_uno.py |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| name: Test unopy via Scipy interface | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ "main" ] | ||
| paths-ignore: | ||
| - '*.md' | ||
| - 'LICENSE' | ||
| - '*.cff' | ||
| - '*.yml' | ||
| - '*.yaml' | ||
| - 'docs/**' | ||
| pull_request: | ||
| branches: [ "main" ] | ||
| paths-ignore: | ||
| - '*.md' | ||
| - 'LICENSE' | ||
| - '*.cff' | ||
| - '*.yml' | ||
| - '*.yaml' | ||
| - 'docs/**' | ||
|
|
||
| env: | ||
| BUILD_TYPE: Debug | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| matrix: | ||
| os: [ubuntu-latest] | ||
| architecture: [x64] | ||
| python-version: ["3.12"] | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Python | ||
| # This is the version of the action for setting up Python, not the Python version. | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| # Semantic version range syntax or exact version of a Python version | ||
| python-version: ${{ matrix.python-version }} | ||
| architecture: ${{ matrix.architecture }} | ||
|
|
||
| - name: Install pybind11 | ||
| run: sudo apt install -y python3-pybind11 pybind11-dev | ||
|
|
||
| - name: Install pytest and scipy | ||
| run: pip install pytest scipy | ||
|
|
||
| - name: Download dependencies | ||
| run: bash dependencies/scripts/download_dependencies.sh | ||
|
|
||
| - name: Compile and install unopy | ||
| run: CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) pip install . -v | ||
|
|
||
| - name: Run tests | ||
| run: python -m pytest --import-mode=importlib ${{github.workspace}}/interfaces/Python/scipy_interface/test_scipy_interface.py |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| # Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU Lesser General Public | ||
| # License version 3 as published by the Free Software Foundation. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| # Lesser General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU Lesser General Public License | ||
| # along with this program; if not, write to the Free Software Foundation, | ||
| # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| # Contributors: | ||
| # INITIAL AUTHORS - initial API and implementation and/or initial | ||
| # documentation | ||
| # :author: François Gallard | ||
| # OTHER AUTHORS - MACROSCOPIC CHANGES | ||
| """The library of Uno constrained gradient-based optimization algorithms.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from typing import Any | ||
| from typing import ClassVar | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from gemseo.algos.design_space_utils import get_value_and_bounds | ||
| from gemseo.algos.opt.base_optimization_library import ( | ||
| OptimizationAlgorithmDescription, | ||
| BaseOptimizationLibrary, | ||
| ) | ||
| from numpy import isfinite | ||
| from numpy import real | ||
|
|
||
| from gemseo_uno.settings.base_uno_settings import UNO_Settings | ||
| from scipy_interface.scipy_uno import minimize | ||
|
|
||
| if TYPE_CHECKING: | ||
| from gemseo.algos.optimization_problem import OptimizationProblem | ||
|
|
||
|
|
||
| @dataclass | ||
| class UnoAlgorithmDescription(OptimizationAlgorithmDescription): | ||
| """The description of the Uno constrained grdient based optimization library.""" | ||
|
|
||
| library_name: str = "Uno" | ||
| """The library name.""" | ||
|
|
||
| handle_equality_constraints: bool = True | ||
| """Whether the optimization algorithm handles equality constraints.""" | ||
|
|
||
| handle_inequality_constraints: bool = True | ||
| """Whether the optimization algorithm handles inequality constraints.""" | ||
|
|
||
| positive_constraints: bool = True | ||
| """Whether the optimization algorithm requires positive constraints.""" | ||
|
|
||
| require_gradient: bool = True | ||
| """Whether the optimization algorithm requires the gradient.""" | ||
|
|
||
| Settings: type[UNO_Settings] = UNO_Settings | ||
| """The option validation model for Uno optimization library.""" | ||
|
|
||
| website: str = "https://unosolver.readthedocs.io/en/latest/" | ||
| """The website of the wrapped library or algorithm.""" | ||
|
|
||
|
|
||
| class UnoOpt(BaseOptimizationLibrary[UNO_Settings]): | ||
| """The library of Uno optimization algorithms.""" | ||
|
|
||
| ALGORITHM_INFOS: ClassVar[dict[str, UnoAlgorithmDescription]] = { | ||
| "UNO_Filter_SQP": UnoAlgorithmDescription( | ||
| algorithm_name="UNO_Filter_SQP", | ||
| description=( | ||
| "Sequential Quadratic Programming (SQP) " | ||
| "implemented in the Uno library" | ||
| ), | ||
| internal_algorithm_name="filtersqp", | ||
| Settings=UNO_Settings, | ||
| ), | ||
| "UNO_Filter_SLP": UnoAlgorithmDescription( | ||
| algorithm_name="UNO_Filter_SLP", | ||
| description=( | ||
| "Sequential Linear Programming (SLP) " "implemented in the Uno library" | ||
| ), | ||
| internal_algorithm_name="filterslp", | ||
| Settings=UNO_Settings, | ||
| ), | ||
| "UNO_Funnel_SQP": UnoAlgorithmDescription( | ||
| algorithm_name="UNO_Funnel_SQP", | ||
| description=( | ||
| "Funnel Sequential Quadratic Programming (SQP)" | ||
| "implemented in the Uno library" | ||
| ), | ||
| internal_algorithm_name="funnelsqp", | ||
| Settings=UNO_Settings, | ||
| ), | ||
| "UNO_IPOPT": UnoAlgorithmDescription( | ||
| algorithm_name="UNO_IPOPT", | ||
| description=( | ||
| "Interior Point Optimization (IPOPT)" "implemented in the Uno library" | ||
| ), | ||
| internal_algorithm_name="ipopt", | ||
| Settings=UNO_Settings, | ||
| ), | ||
| } | ||
|
|
||
| def _run(self, problem: OptimizationProblem) -> tuple[str, Any]: | ||
| # Get the normalized bounds: | ||
| x_0, l_b, u_b = get_value_and_bounds( | ||
| problem.design_space, self._settings.normalize_design_space | ||
| ) | ||
| # Replace infinite values with None: | ||
| l_b = [val if isfinite(val) else None for val in l_b] | ||
| u_b = [val if isfinite(val) else None for val in u_b] | ||
| bounds = list(zip(l_b, u_b, strict=False)) | ||
|
|
||
| # Get constraint in SciPy format | ||
| scipy_constraints = [ | ||
| { | ||
| "type": constraint.f_type, | ||
| "fun": constraint.evaluate, | ||
| "jac": constraint.jac, | ||
| } | ||
| for constraint in self._get_right_sign_constraints(problem) | ||
| ] | ||
|
|
||
| # Filter settings to get only the uno ones | ||
| settings_ = self._filter_settings(self._settings.model_dump(), UNO_Settings) | ||
|
|
||
| # Deactivate stopping criteria which are handled by GEMSEO | ||
| tolerance = 0.0 | ||
|
|
||
| opt_result = minimize( | ||
| fun=lambda x: real(problem.objective.evaluate(x)), | ||
| jac=problem.objective.jac, | ||
| x0=x_0, | ||
| method=self.ALGORITHM_INFOS[self._algo_name].internal_algorithm_name, | ||
| bounds=bounds, | ||
| constraints=scipy_constraints, | ||
| options=settings_, | ||
| tol=tolerance, | ||
| ) | ||
|
|
||
| return opt_result.message, opt_result.status | ||
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
Power2test).There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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
Power2case, 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 withAlgorithmic 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.
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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.