Skip to content

Conversation

andyfaff
Copy link
Contributor

Transitions optimize.differential_evolution according to SPEC007.

@mdhaber, @tupui.

@github-actions github-actions bot added scipy.optimize scipy._lib maintenance Items related to regular maintenance tasks labels Oct 29, 2024
Copy link
Member

@tupui tupui left a comment

Choose a reason for hiding this comment

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

Big yes for me. I have eggs in the basket so I will let the crowd decide. Approving if that counts in any ways.

@andyfaff
Copy link
Contributor Author

The biggest effect of PRs of this type will be the default RNG will be a Generator.

Previously if one supplied an int then the seeded RNG would be RandomState, now it'll be Generator.
Previously if one didn't specify seed then the RNG would be RandomState.

This is going to change random number generation, and code that relied on a certain seed for reproducibility will be different across versions.

@mdhaber
Copy link
Contributor

mdhaber commented Oct 30, 2024

Thanks @andyfaff for doing this!

This is going to change random number generation, and code that relied on a certain seed for reproducibility will be different across versions.

The intent of the SPEC is that this only happens after warnings expire. In the meantime, arguments passed positionally or via the old keyword argument are processed as they always were, whatever that is. (Arguments passed using the new keyword are validated and standardarized by the decorator according to the new behavior, which is simply to call rng = np.random.default_rng(rng).)

After the warnings have been emitted for two cycles (according to SciPy's policy), then we can remove the decorator and any old argument validation/standardization code except for rng = np.random.default_rng(rng).

Co-authored-by: Matt Haberland <mhaberla@calpoly.edu>
@andyfaff
Copy link
Contributor Author

andyfaff commented Oct 30, 2024

@mdhaber what the SPEC doesn't provide guidelines on is what should happen to the docstring of differential_evolution.
For the initial state of the change:

  • should there still be documentation on what happens if seed (random_state in the SPEC example) is specified?
  • should it just specify the rng behaviour?
  • should there be documentation on both keywords?
  • should it mention the SPEC?

@andyfaff andyfaff added this to the 1.15.0 milestone Oct 31, 2024
@andyfaff
Copy link
Contributor Author

Ok, I think I got a useful docstring now.

@mdhaber
Copy link
Contributor

mdhaber commented Oct 31, 2024

@mdhaber what the SPEC doesn't provide guidelines on is what should happen to the docstring

It was actually an explicit decision to leave that up to projects due to differences in opinion over how (/whether) the outgoing behavior should be documented explicitly. One side is that only the new behavior needs to be documented, since old code will continue to work and new uses should follow the new pattern. The other is that we need to document all supported behaviors of the function - both as a matter of principle and to support those who are referring to the documentation while working with existing code that uses the old behavior.

Regardless of the level of detail about the supported old behavior, I'd suggest leading with the new behavior to encourage its use. These things are true whether the argument is passed by position or by keyword:

    rng : `numpy.random.Generator`, optional
        Pseudorandom number generator state.

Then, if you prefer, I think it's OK to document all the details as long as it's clear that passing a Generator is the recommended use. In this interim period:

  • If rng is passed by keyword, types other than numpy.random.Generator are passed to numpy.random.default_rng to instantiate a Generator. That is really all that needs to be said about the new behavior. (It doesn't include the final suggested phrasing "When rng is None, a new numpy.random.Generator is created using entropy from the operating system", but that's not really needed - it's just a description of what numpy.random.default_rng always does.)
  • If the argument is passed by position or seed is passed by keyword, the behavior is whatever the old behavior was (to maintain backward compatibility).

Thanks for pioneering this transition. I know it's messy!

@andyfaff
Copy link
Contributor Author

How about:

    rng : {None, int, `numpy.random.Generator`}, optional
        
        ..versionchanged:: 1.15.0
            As part of the `SPEC-007 <https://scientific-python.org/specs/spec-0007/>`_
            transition from use of `numpy.random.RandomState` to
            `numpy.random.Generator` this keyword was changed from `seed` to `rng`.
            For an interim period both keywords will continue to work (only specify
            one of them). After the interim period using the `seed` keyword will emit
            warnings. The behavior of the `seed` and `rng` keywords is outlined below.

        If `rng` is passed by keyword, types other than `numpy.random.Generator` are
        passed to `numpy.random.default_rng` to instantiate a `Generator`.
        If `rng` is already a `Generator` instance, then the provided instance is
        used.
        
        If this argument is passed by position or `seed` is passed by keyword, the
        behavior is:
        
        - If `seed` is None (or `np.random`), the `numpy.random.RandomState`
        singleton is used.
        - If `seed` is an int, a new `RandomState` instance is used,
        seeded with `seed`.
        - If `seed` is already a `Generator` or `RandomState` instance then
        that instance is used.
        
        Specify `seed`/`rng` for repeatable minimizations.

@mdhaber
Copy link
Contributor

mdhaber commented Nov 1, 2024

I think that's OK.

Just some rendering issues, especially with the admonition:
image

We never saw this rendered, so I think we need to do something about the single backticks around plain Generator in the SPEC 7 example. Maybe there's a way to get it to link as intended without specifying the full name (numpy.random.Generator) every time, but intersphinx always confuses me. There are a lot more uses of Generator, RandomState, and np.random here that are probably supposed to render as links.

@stefanv Just thought I'd ping you so you can see how one of these SPEC 7 transitions to rng is playing out.

@andyfaff
Copy link
Contributor Author

andyfaff commented Nov 1, 2024

I've tried to improve by putting more backticks around e.g. Generator. We don't need multiple links everywhere it's mentioned, and using something like :class:np.random.Generator makes it harder to look at when looking at the plain text version.

@andyfaff
Copy link
Contributor Author

andyfaff commented Nov 1, 2024

I don't know why the versionchanged isn't displayed properly.

@andyfaff
Copy link
Contributor Author

andyfaff commented Nov 2, 2024

It's rendering properly now.

Copy link
Contributor

@mdhaber mdhaber left a comment

Choose a reason for hiding this comment

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

I think this is ready. If you're happy with the final tweaks @andyfaff, I'll go ahead and merge. SciPy already endorsed SPEC 7.

@lucascolley lucascolley changed the title MAINT: SPEC007, transition differential_evolution to Generator API: optimize.differential_evolution: transition to Generator (SPEC 7) Nov 5, 2024
@lucascolley
Copy link
Member

I'll go ahead and merge. SciPy already endorsed SPEC 7.

Fine to merge, but it wasn't decided in the endorsement thread that we were also happy to implement the SPEC everywhere in SciPy (they are two separate things). The relevant news from the forum is that Andrew's post for this API change received no pushback.

@andyfaff
Copy link
Contributor Author

andyfaff commented Nov 5, 2024

@mdhaber, I'm happy with the changes you made.

Once this gets merged I'm intending to start working on similar changes. First the other global optimisers, then stats.qmc.

I think it would be good to do all the stats distributions as well, but that may be a big task.

@mdhaber mdhaber merged commit 8db8672 into scipy:main Nov 5, 2024
36 checks passed
@mdhaber
Copy link
Contributor

mdhaber commented Nov 5, 2024

Thanks @andyfaff!

I think that sounds pretty good, although I wouldn't put time into the old distributions, personally. The new distributions will only support the rng keyword and behavior, and for new code, they will have superseded the old ones by (well before, I think ..) the end of the two year SPEC 7 plan. Other stats stuff that accepts the old arguments would be fair game, though.

rng=1,
tol=0.5)
# use of rng=RandomState should give rise to an error.
differential_evolution(self.quadratic,
Copy link
Member

Choose a reason for hiding this comment

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

I think these need to be in separate context managers as the test will still pass if only one these raises an error

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup. Let's fix that in the next PR like this.

@lucascolley lucascolley added the needs-release-note a maintainer should add a release note written by a reviewer/author to the wiki label Nov 5, 2024
@andyfaff andyfaff deleted the spec7 branch November 6, 2024 08:47
@mdhaber mdhaber removed the needs-release-note a maintainer should add a release note written by a reviewer/author to the wiki label Nov 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintenance Items related to regular maintenance tasks scipy._lib scipy.optimize
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants