Skip to content
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

[0.46] Finalise support for Numpy 2.0 #12189

Merged
merged 5 commits into from
Jun 18, 2024

Conversation

jakelishman
Copy link
Member

Summary

This commit brings the Qiskit test suite to a passing state (with all optionals installed) with Numpy 2.0.0b1, building on previous commits that handled much of the rest of the changing requirements:

Notably, this commit did not actually require a rebuild of Qiskit, despite us compiling against Numpy; it seems to happen that the C API stuff we use via rust-numpy (which loads the Numpy C extensions dynamically during module initialisation) hasn't changed.

The main changes are:

  • adapting to the changed copy=None and copy=False semantics in array and asarray.
  • making sure all our implementers of __array__ accept both dtype and copy arguments.

Co-authored-by: Lev S. Bishop 18673315+levbishop@users.noreply.github.com


Update __array__ methods for Numpy 2.0 compatibility

As of Numpy 2.0, implementers of __array__ are expected and required to have a signature

def __array__(self, dtype=None, copy=None): ...

In Numpys before 2.0, the copy argument will never be passed, and the expected signature was

def __array__(self, dtype=None): ...

Because of this, we have latitude to set copy in our implementations to anything we like if we're running against Numpy 1.x, but we should default to copy=None if we're running against Numpy 2.0.

The semantics of the copy argument to np.array changed in Numpy 2.0. Now, copy=False means "raise a ValueError if a copy is required" and copy=None means "copy only if required". In Numpy 1.x, copy=False meant "copy only if required". In both Numpy 1.x and 2.0, ndarray.astype takes a copy argument, and in both, copy=False means "copy only if required". In Numpy 2.0 only, np.asarray gained a copy argument with the same semantics as the np.array copy argument from Numpy 2.0.

Further, the semantics of the __array__ method changed in Numpy 2.0, particularly around copying. Now, Numpy will assume that it can pass copy=True and the implementer will handle this. If copy=False is given and a copy or calculation is required, then the implementer is required to raise ValueError. We have a few places where the __array__ method may (or always does) calculate the array, so in all these, we must forbid copy=False.

With all this in mind: this PR sets up all our implementers of __array__ to either default to copy=None if they will never actually need to use the copy argument within themselves (except perhaps to test if it was set by Numpy 2.0 to False, as Numpy 1.x will never set it), or to a compatibility shim _numpy_compat.COPY_ONLY_IF_NEEDED if they do naturally want to use it with those semantics. The pattern

def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
    dtype = self._array.dtype if dtype is None else dtype
    return np.array(self._array, dtype=dtype, copy=copy)

using array instead of asarray lets us achieve all the desired behaviour between the interactions of dtype and copy in a way that is compatible with both Numpy 1.x and 2.x.

Details and comments

This is the spiritual backport of #11999.

jakelishman and others added 2 commits April 16, 2024 14:12
This commit brings the Qiskit test suite to a passing state (with all
optionals installed) with Numpy 2.0.0b1, building on previous commits
that handled much of the rest of the changing requirements:

- Qiskitgh-10890
- Qiskitgh-10891
- Qiskitgh-10892
- Qiskitgh-10897
- Qiskitgh-11023

Notably, this commit did not actually require a rebuild of Qiskit,
despite us compiling against Numpy; it seems to happen that the C API
stuff we use via `rust-numpy` (which loads the Numpy C extensions
dynamically during module initialisation) hasn't changed.

The main changes are:

- adapting to the changed `copy=None` and `copy=False` semantics in
  `array` and `asarray`.
- making sure all our implementers of `__array__` accept both `dtype`
  and `copy` arguments.

Co-authored-by: Lev S. Bishop <18673315+levbishop@users.noreply.github.com>
As of Numpy 2.0, implementers of `__array__` are expected and required
to have a signature

    def __array__(self, dtype=None, copy=None): ...

In Numpys before 2.0, the `copy` argument will never be passed, and the
expected signature was

    def __array__(self, dtype=None): ...

Because of this, we have latitude to set `copy` in our implementations
to anything we like if we're running against Numpy 1.x, but we should
default to `copy=None` if we're running against Numpy 2.0.

The semantics of the `copy` argument to `np.array` changed in Numpy 2.0.
Now, `copy=False` means "raise a `ValueError` if a copy is required" and
`copy=None` means "copy only if required".  In Numpy 1.x, `copy=False`
meant "copy only if required".  In _both_ Numpy 1.x and 2.0,
`ndarray.astype` takes a `copy` argument, and in both, `copy=False`
means "copy only if required".  In Numpy 2.0 only, `np.asarray` gained a
`copy` argument with the same semantics as the `np.array` copy argument
from Numpy 2.0.

Further, the semantics of the `__array__` method changed in Numpy 2.0,
particularly around copying.  Now, Numpy will assume that it can pass
`copy=True` and the implementer will handle this.  If `copy=False` is
given and a copy or calculation is required, then the implementer is
required to raise `ValueError`.  We have a few places where the
`__array__` method may (or always does) calculate the array, so in all
these, we must forbid `copy=False`.

With all this in mind: this PR sets up all our implementers of
`__array__` to either default to `copy=None` if they will never actually
need to _use_ the `copy` argument within themselves (except perhaps to
test if it was set by Numpy 2.0 to `False`, as Numpy 1.x will never set
it), or to a compatibility shim `_numpy_compat.COPY_ONLY_IF_NEEDED` if
they do naturally want to use it with those semantics.  The pattern

    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
        dtype = self._array.dtype if dtype is None else dtype
        return np.array(self._array, dtype=dtype, copy=copy)

using `array` instead of `asarray` lets us achieve all the desired
behaviour between the interactions of `dtype` and `copy` in a way that
is compatible with both Numpy 1.x and 2.x.
@jakelishman jakelishman added Changelog: New Feature Include in the "Added" section of the changelog dependencies Pull requests that update a dependency file labels Apr 16, 2024
@jakelishman jakelishman added this to the 0.46.2 milestone Apr 16, 2024
@jakelishman jakelishman requested review from nonhermitian and a team as code owners April 16, 2024 14:15
@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

Copy link
Contributor

@raynelfss raynelfss left a comment

Choose a reason for hiding this comment

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

LGTM, thank you for working on this.

@raynelfss raynelfss added this pull request to the merge queue Jun 18, 2024
Merged via the queue into Qiskit:stable/0.46 with commit 44b3146 Jun 18, 2024
10 checks passed
@jakelishman jakelishman deleted the backport/numpy-2.0 branch June 18, 2024 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog dependencies Pull requests that update a dependency file
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants