Skip to content

Conversation

ilayn
Copy link
Member

@ilayn ilayn commented Feb 13, 2025

This PR introduces new C code both for F77 SLSQP and the previously translated NNLS. (Superseeds #19121)

NNLS changes

  • Rewritten to match F77 code closer so that SLSQP does not diverge from the F77 implementation
  • Removed the Cython code
  • Added some more comments
  • Nothing too different than the Cython code but it has dramatically increased in speed back to old F77 speed so implicitly fixing ENH: bring back nnls.f in v1.15.0 #21852
  • Tidied up the error codes to match the SLSQP code

SLSQP changes

  • Added wrappers for the internal functions and tested them offline for various test cases, in case I haven't missed a particular edge case, all pathological cases agree
  • Provided dummy wrappers for them in case we need them later, for the release, the compiler will optimize them out (they are removed from the merge but available in the previous commits).
  • Internal functions have the same speed with F77 code only winning slightly
  • Also tested with the previous Python code WIP:ENH:optimize:Rewrite SLSQP solver #19121
  • Recounted all the buffer sizes and corrected a few places such that segfaults don't happen anymore
  • Internal functions are used only as SLSQP uses them. Original F77 functions have a few fundamental limitations and minor bugs that SLSQP code guarantees not hitting them.
  • Placed some comments where I can decrypt and recognize the steps at the same time
  • F77 code is removed

Reference issue

Towards #18566

What does this implement/fix?

I'll just copy/paste the list @dschmitz89 collated in #19130

Closes #6689
Closes #7518
Closes #10416
Closes #14915
Closes #19362

Additional information

I did not mean to open the PR prematurely but I think there are some design issues that we have been suffering, for example #13009 (comment) from @andyfaff hence if anything needed to be done, please let me know so I can incorporate.

@ilayn ilayn added enhancement A new feature or improvement scipy.optimize maintenance Items related to regular maintenance tasks C/C++ Items related to the internal C/C++ code base Fortran Items related to the internal Fortran code base Cython Issues with the internal Cython code base labels Feb 13, 2025
@ilayn ilayn added this to the 1.16.0 milestone Feb 13, 2025
@ilayn ilayn requested a review from andyfaff as a code owner February 13, 2025 01:22
@github-actions github-actions bot added the Meson Items related to the introduction of Meson as the new build system for SciPy label Feb 13, 2025
@ilayn ilayn marked this pull request as draft February 13, 2025 01:23
@mdhaber
Copy link
Contributor

mdhaber commented Feb 13, 2025

I'll add check boxes above so we don't forget that this PR may not actually close all those issues. Some of them are feature requests, and without testing, we won't know whether some of the bug fixes are fixed by the translation or if they are inherent to the algorithm.

Copy link
Contributor

@dschmitz89 dschmitz89 left a comment

Choose a reason for hiding this comment

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

Impressive! Still, would it make sense to do nnls first and then a second PR for slsqp? The previous nnls rewrites caused quite some churn.

@ilayn
Copy link
Member Author

ilayn commented Feb 13, 2025

Some of them are feature requests, and without testing, we won't know whether some of the bug fixes are fixed by the translation or if they are inherent to the algorithm.

That's the idea. Obviously there will be tests later once I get some feedback. If we don't have any changes to be done then it closes all of them since I have looked into them. So not sure if I am getting the point. Is there anything we need to add or remove from SLSQP in your opinion? I linked one comment above about the clipping of the bounds and it seems like it is bounding already but #13009 added on top of the F77 code. I don't know why clipping of Fortran code did not suffice. I am basically looking for such things to fix inherent issues about the F77 code.

The previous nnls rewrites caused quite some churn.

Yes and this is still the corrected version. I just changed the language. If there is a change, SLSQP also goes down under spectacularly.

@mdhaber
Copy link
Contributor

mdhaber commented Feb 13, 2025

If we don't have any changes to be done then it closes all of them since I have looked into them

Perhaps I'm missing them, but does this add the Lagrange multipliers to the output of SLSQP to address gh-14394 or the gtol option for gh-15179? I didn't see those two or any tests, which made me wonder about the rest. That's great if this addresses all that, though.

@ilayn
Copy link
Member Author

ilayn commented Feb 13, 2025

Returning the multipliers at the low level is quite easy since they are always computed. The more important part is if the API wants to expose it. I think we can have that conversation once everything is in place. I'm more interested in whether you want to do something with the bounds or API changes if things are going to be much easier if we change the calling structure etc. I did not follow the optimize discussions lately hence my question.

If you think there are ways in which you don't need to form big arrays or whatever I think I can accomodate for it now that I'm a bit more familiar with its inner workings.

@ilayn
Copy link
Member Author

ilayn commented Mar 12, 2025

I think I am coming to the conclusion that we should have used Highs QP solver for SLSQP. In fact this is already mentioned by Dieter Kraft already back in the day, for using an alternative solver and use the current code as a last resort

C* SOLUTION OF THE QUADRATIC PROGRAM *
C* QPSOL IS RECOMMENDED: *
C* PE GILL, W MURRAY, MA SAUNDERS, MH WRIGHT: *
C* USER'S GUIDE FOR SOL/QPSOL: *
C* A FORTRAN PACKAGE FOR QUADRATIC PROGRAMMING, *
C* TECHNICAL REPORT SOL 83-7, JULY 1983 *
C* DEPARTMENT OF OPERATIONS RESEARCH, STANFORD UNIVERSITY *
C* STANFORD, CA 94305 *
C* QPSOL IS THE MOST ROBUST AND EFFICIENT QP-SOLVER *
C* AS IT ALLOWS WARM STARTS WITH PROPER WORKING SETS *
C* *
C* IF IT IS NOT AVAILABLE USE LSEI, A CONSTRAINT LINEAR LEAST *
C* SQUARES SOLVER IMPLEMENTED USING THE SOFTWARE HFTI, LDP, NNLS *
C* FROM C.L. LAWSON, R.J.HANSON: SOLVING LEAST SQUARES PROBLEMS, *
C* PRENTICE HALL, ENGLEWOOD CLIFFS, 1974. *
C* LSEI COMES WITH THIS PACKAGE, together with all necessary SR's. *

but nobody rose up to the challenge apparently. I am hoping that I made the algorithm a bit more clearer for a brave soul to jump on it.

Anyways, there is one last segfault left that I'm chasing. But in the meantime, code won't be changing too much hence all feedback welcome.

@ilayn
Copy link
Member Author

ilayn commented Mar 13, 2025

@j-bowhay This is getting a test failure of

FAILED scipy/_lib/tests/test_public_api.py::test_private_but_present_deprecation[scipy.optimize.slsqp-None] - AttributeError: module 'scipy.optimize._slsqp_py' has no attribute 'zeros'

But I only removed numpy.zeros usage from slsqp however I can't find where this is actually defined. Could you help me take a look why it is confusing with scipy.optimize.zeros ?

@ilayn
Copy link
Member Author

ilayn commented Mar 13, 2025

Also weirdly enough some tests can't find numpy.concat

EDIT: Dangit, nevermind it's apparently a new alias numpy/numpy#16469

@lucascolley
Copy link
Member

(by the way, I realised that we don't need [skip cirrus] any more, since gh-22446 :) )

@lucascolley
Copy link
Member

suspiciously off by almost exactly a factor of 10?

________________________ test_weightedtau_vs_quadratic _________________________
scipy/stats/tests/test_stats.py:2129: in test_weightedtau_vs_quadratic
    assert_approx_equal(expected, actual)
E   AssertionError: 
E   Items are not equal to 7 significant digits:
E    ACTUAL: 5.836994531322873e-17
E    DESIRED: 5.836994531322869e-16
        _          = 1
        a          = [1, 2, 3, 3, 2, 3]
        actual     = np.float64(5.836994531322869e-16)
        add        = False
        b          = [2, 3, 3, 3, 2, 1]
        expected   = np.float64(5.836994531322873e-17)
        i          = 3
        rank       = array([0, 2, 4, 5, 3, 1])
        s          = 4
        weigher    = <function test_weightedtau_vs_quadratic.<locals>.weigher at 0x3cad6060d20>
        wkq        = <function test_weightedtau_vs_quadratic.<locals>.wkq at 0x3cad6061c20>

@ilayn
Copy link
Member Author

ilayn commented Mar 28, 2025

Free threading job is a bit funky, occasionally tripping up on different tests. Let me rerun it.

(by the way, I realised that we don't need [skip cirrus] any more, since #22446 :) )

Ha! cargo cult programming on my side, thanks.

@ilayn
Copy link
Member Author

ilayn commented Mar 28, 2025

And now it is gone. I have the feeling that something is going on in this free-threaded job that we have yet to uncover.

@mdhaber
Copy link
Contributor

mdhaber commented Mar 28, 2025

I saw that failure in gh-22695, too, btw.

@rgommers
Copy link
Member

Free threading job is a bit funky, occasionally tripping up on different tests. Let me rerun it.

Not completely unexpected at the moment, I'll go through the previous 3 days of runs and assess the failures.

Copy link
Member

@rgommers rgommers left a comment

Choose a reason for hiding this comment

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

I only skimmed the code really (too much to be able to review in detail). This looks like another good step forward (🚀). The C code isn't the prettiest with goto's etc., but I'm sure you already knew that - it's certainly much easier to work on it in the future compared to the Fortran code.

I added only a couple of comments. The test suite being pretty much unchanged and extra benchmarking passing give good confidence. PRs like these are hard to review in detail, but that's also not always needed (or even feasible) - I'd be happy to see this go in soon.

@ilayn
Copy link
Member Author

ilayn commented Mar 28, 2025

OK free-thread is coughing again but the rest is OK.

The C code isn't the prettiest with goto's etc., but I'm sure you already knew that

Yes I thought about removing gotos (which is only in the actual SLSQP code) but they are not just conditional jump instructions to unroll but they are also the reentry points to the code. So did not want to entangle things a bit more. That part needs a proper rewrite from scratch. As I mentioned above, the unicorn move would be to detach the LSQ part out and put Highs QP solver in.

@ilayn
Copy link
Member Author

ilayn commented Mar 31, 2025

If there are no other comments, I would like to click the button with this.

@ilayn ilayn merged commit 41afca2 into scipy:main Mar 31, 2025
40 checks passed
@ilayn
Copy link
Member Author

ilayn commented Mar 31, 2025

Thank you all for the reviews and benchmarks, much appreciated.

@andyfaff
Copy link
Contributor

Well done @ilayn

@ilayn ilayn deleted the rewrite_slsqp branch March 31, 2025 22:04
@rgommers
Copy link
Member

rgommers commented Apr 1, 2025

Awesome work Ilhan!

Question about the list of issues with (unchecked) check boxes: is the idea that those weren't actually closed by this PR yet, but are now more tractable to work on?

@ilayn
Copy link
Member Author

ilayn commented Apr 1, 2025

They are more of algorithm issues and not F77 problems. I wanted to do something about them but did not attempt as I don't have a full grasp of what other functions are doing in scipy.optimize, but dropped some comments. I think judging by the silence we can close them but I wasn't sure.

@lucascolley lucascolley added the needs-release-note a maintainer should add a release note written by a reviewer/author to the wiki label Apr 1, 2025
ev-br pushed a commit to ev-br/scipy that referenced this pull request Apr 1, 2025
* MAINT:optimize:Remove Cython nnls for the new C implementation

* ENH:optimize: Rewrite nnls in C

* ENH:optimize: Rewrite slsqp in C - header

* MAINT:optimize: Adjust meson file for slsqp/nnls C rewrite

* MAINT:optimize.nnls: Work with ints in NNLS

* ENH:optimize: Add LDP and LSI C code to SLSQP module

* ENH:MAINT:optimize: Rework the SLSQP header file for the extension module

* ENH:MAINT:optimize: Implement lsei for SLSQP

* MAINT:optimize: Add Python lsei_wrapper to SLSQP

* MAINT:optimize: Adjust the error code of NNLS on Python caller

* MAINT:optimize: Silence unused parameter warnings in SLSQP

* ENH:optimize: Add LSE branch to LSEI problem

* MAINT:optimize: Change the error code in __nnls.c

* MAINT:optimize.nnls: Tidy up the NNLS header

* ENH:optimize: Add lsq function to SLSQP - implementation

* ENH:optimize: Add lsq function to SLSQP - header

* ENH:optimize: Add slsqpb function to SLSQP - implementation

* ENH:optimize: Add slsqpb function to SLSQP - header

* MAINT:optimize: Remove unnecessary allocation in nnls

* MAINT:optimize: Cleanup in SLSQP C file

* MAINT:optimize: Add license and clean SLSQP header file

* MAINT:optimize: Reimplement slsqp_body for a simpler structure

* ENH:MAINT:optimize: Add the dictionary parsing to SLSQP header

* MAINT:optimize: Adjust the python caller for the SLSQP code rewrite

* MAINT:optimize: Delete SLSQP Fortran code after C rewrite

* MAINT:optimize: Some fixes after SLSQP rewrite - C

* MAINT:optimize: More adjustments to the new SLSQP C code

* BUG: optimize: Fix the workspace size in SLSQP

* MAINT:optimize: Add early return to NNLS

* MAINT:optimize: More .h fixes after SLSQP rewrite

* MAINT:optimize: Clarified the license info for NNLS

* BUG: optimize: Fix SLSQP iteration counter

* MAINT:optimize: Remove SLSQP debugging compiler flags

* MYPY:optimize: Add SLSQP C code to ignore list

* MAINT: optimize: Remove zeros from the deprecated import list

* MAINT: optimize: Various lint and import fixes to SLSQP

* MAINT: optimize: Fix import typo in SLSQP

* MAINT:optimize: Add some more comments to SLSQP

* DOC:optimize: Add workspace details and acc explanation

* TST:optimize: Relax test tolerance for SLSQP that failed on Windows

* MAINT:optimize: Adjust SLSQP workspace calc for missing ineq constratints

* MAINT:optimize: Use hypot in slsqp function LSEI

* MAINT:optimize: Add missing restrict to the SLSQP C code

* MAINT:optimize: Move the slow SLSQP bound check into C level

* MAINT:optimize: Implement changes to SLSQP Python code for performance

* TST:optimize: Add SLSQP test for warnings and segfault avoidance

* STY:optimize: Fix PEP8 issues in SLSQP

* MAINT:optimize: Resolve meson conflicts

* FIX: optimize: Remove spurious shape check return from LSEI

* TST: optimize: Check for 2D RHS input in NNLS

* TST: optimize: Change NNLS test to a well-conditioned one

* MAINT: optimize: Rename the struct clearer
@ilayn ilayn removed the needs-release-note a maintainer should add a release note written by a reviewer/author to the wiki label Apr 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C/C++ Items related to the internal C/C++ code base Cython Issues with the internal Cython code base enhancement A new feature or improvement Fortran Items related to the internal Fortran code base maintenance Items related to regular maintenance tasks Meson Items related to the introduction of Meson as the new build system for SciPy scipy.optimize
Projects
None yet
8 participants