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

array: implement array resize #795

Merged
merged 1 commit into from
Apr 29, 2017
Merged

Conversation

uentity
Copy link
Contributor

@uentity uentity commented Apr 11, 2017

Resize won't work in every conditions, but at least it should work

  1. if new array was created from scratch in C++ and resized before being exported to Python
  2. if array passed from Python, owns it's data and doesn't have dependant arrays and refcheck is 0.

@@ -158,6 +163,7 @@ struct npy_api {
Py_ssize_t *, PyObject **, PyObject *);
PyObject *(*PyArray_Squeeze_)(PyObject *);
int (*PyArray_SetBaseObject_)(PyObject *, PyObject *);
PyObject* (*PyArray_Resize_)(pybind11::detail::PyArray_Proxy*, PyArray_Dims*, int, int);
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 the first argument should just be a PyObject *, to be consistent with the other function pointers (e.g. PyArray_Squeeze_).

@@ -646,6 +654,26 @@ class array : public buffer {
return reinterpret_steal<array>(api.PyArray_Squeeze_(m_ptr));
}

/// Resize array to given shape
/// If refcheck is true, then resize will fail if > 1 references exists to this array
void resize(const std::vector<size_t> &new_shape, bool refcheck = false) {
Copy link
Member

Choose a reason for hiding this comment

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

I strongly feel that refcheck should default to true. Bypassing a reference check can be pretty dangerous, and should never be the default.

Copy link
Member

Choose a reason for hiding this comment

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

Also the comment here is a little bit wrong: resize fails with > 1 reference only if changing the total size. If the size doesn't change, a resize should be fine. (It's probably also worth changing the test to explicitly test this; at = a.transpose() is a simple way to add a reference to a).

Copy link
Member

Choose a reason for hiding this comment

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

+1 for refcheck=true by default.

Copy link
Member

Choose a reason for hiding this comment

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

Also note that in the Python API, refcheck defaults to True.

// try to resize, set order to -1 cause it's not used anyway
new_array = detail::npy_api::get().PyArray_Resize_(
detail::array_proxy(m_ptr), &d, int(refcheck), -1
);
Copy link
Member

Choose a reason for hiding this comment

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

Rather than use raw pointers, better to use pybind::object to make sure you get proper reference handling. This should work:

object new_array = reinterpret_steal<object>(detail::npy_api::get().PyArray_Resize_(
    detail::array_proxy(m_ptr), &d, int(refcheck), -1
));
if (!new_array) throw error_already_set();
if (isinstance<array>(new_array)) {
    *this = std::move(new_array);
}

The main difference, aside from simplifying the code a bit, is that when the return is PyNone, you still properly decref it at the end of the function when the object is destroyed (unless you move it away first). Right now it leaks a reference--which probably isn't that big a deal with None, but still.

Somewhat strange to me about PyArray_Resize() is that, looking at the source code, I can't see how it ever returns anything other than None (aside from NULL returned on error).

@@ -377,3 +377,18 @@ def test_array_unchecked_dyn_dims(msg):

assert proxy_auxiliaries2_dyn(z1) == [11, 11, True, 2, 8, 2, 2, 4, 32]
assert proxy_auxiliaries2_dyn(z1) == array_auxiliaries2(z1)

def test_array_resize(msg):
Copy link
Member

Choose a reason for hiding this comment

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

You should also test some failure conditions here, i.e. that you get the expected exception and exception message if attempting to resize a referenced array.

@@ -264,4 +264,24 @@ test_initializer numpy_array([](py::module &m) {
sm.def("array_auxiliaries2", [](py::array_t<double> a) {
return auxiliaries(a, a);
});

// resize array to 3D without changing size
Copy link
Member

Choose a reason for hiding this comment

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

3D -> 2D

// resize array to 3D without changing size
// should probably succeed
sm.def("array_reshape2", [](py::array_t<double> a) {
const size_t dim_sz(std::sqrt(a.size()));
Copy link
Member

Choose a reason for hiding this comment

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

A self-commenting input validation test:

if (dim_sz * dim_sz != a.size())
    throw std::domain_error("array_reshape2: input array total size is not a squared integer");

@jagerman
Copy link
Member

A few things to fix, but nothing major: this looks pretty good to me.

@dean0x7d
Copy link
Member

There is also the possibility of simplifying the implementation so it doesn't need the Numpy C API:

void resize(const std::vector<size_t> &new_shape, bool refcheck = true) {
    attr("resize")(new_shape, refcheck);
}

Which would be a bit slower because of the dynamic method lookup, but at the same time I don't think resize is going into any hot loops anyway. (I don't mind the C API either, I'm just mentioning this as a possibility.)

void resize(const std::vector<size_t> &new_shape, bool refcheck = false) {
/// If refcheck is true and more that one reference exist to this array
/// then resize will succeed only if it makes a reshape, i.e. original size doesn't change
void resize(const std::vector<size_t> &new_shape, bool refcheck = true) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not a huge fan of taking concrete containers (std::vector<size_t>) like this--it's restrictive and something of a nuisance if you happen to have your shape in some other container. Just to be clear: this isn't your fault at all; what you wrote here is perfectly consistent with the current API. But I'm aiming to solve it with PR #788, which would allow you to change the signature to void resize(ShapeContainer new_shape, bool refcheck = true), where the ShapeContainer is implicitly convertible from any sort of container, copy the contents into a std::vector<Py_intptr_t>. That would also allow you to drop both the reinterpret_cast and const_cast from:
reinterpret_cast<Py_intptr_t*>(const_cast<size_t*>(new_shape.data())), int(new_shape.size())
replacing it with just:
new_shape->data(), int(new_shape.size())

Copy link
Member

Choose a reason for hiding this comment

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

I've merged #788, so you should be able to switch the input to a ShapeContainer new_shape now. (Unfortunately I also caused some conflicts--both had tests added in the same place--but I added a commit to hopefully resolve them).

@aldanor
Copy link
Member

aldanor commented Apr 13, 2017

(Minor: it's best to not have merge commits from master in feature branches, ideally this PR would get rebased or squashed)

@jagerman
Copy link
Member

Yeah, it'll get squashed and rebased before being merged.

@@ -129,6 +129,11 @@ struct npy_api {
NPY_STRING_, NPY_UNICODE_, NPY_VOID_
};

typedef struct {
const Py_intptr_t *ptr;
Copy link
Contributor Author

@uentity uentity Apr 13, 2017

Choose a reason for hiding this comment

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

I think it's safe to make ptr const here instead of having const_cast<Py_intptr_t*>(new_shape->data()) later in resize()

Copy link
Member

@jagerman jagerman Apr 13, 2017

Choose a reason for hiding this comment

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

Just change the const ShapeContainer &new_shape to ShapeContainer new_shape; it's essentially always going to be passed by value (via implicit conversion), so the const reference doesn't gain anything.

Edit: the point being to keep the definition identical to the one numpy defines.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -264,7 +264,28 @@ test_initializer numpy_array([](py::module &m) {
return auxiliaries(a, a);
});

// Issue #785: Uninformative "Unknown internal error" exception when constructing array from empty object:
// Issue #785: Uninformative "Unknown internal error" exception when constructing array from empty object:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops

@jagerman
Copy link
Member

You can ignore the MSVC failure; that's something broken in appveyor (#792).

@uentity
Copy link
Contributor Author

uentity commented Apr 14, 2017

Travis also didn't complete for some reason...

@jagerman
Copy link
Member

I just restarted the failed job.

@jagerman
Copy link
Member

Can you rebase this against current master? I have a feeling the travis-ci commit I merged today is going to cause some failures in the PyPy test here from some things numpy doesn't support when under PyPy (and so need to be skipped for PyPy).

@uentity
Copy link
Contributor Author

uentity commented Apr 19, 2017

Rebased.
I failed to build tests with latest PyPy (5.7.1) as well as with bundled with Fedora 25 (5.4.0), so didn't check if tests succeeds on it.

@jagerman
Copy link
Member

Don't worry about 5.4.0; we don't support anything before 5.7 as it had several fixes we reported to get it working with pybind. The one test failure was exactly what I expected to fail; just skip that test under PyPy: move the failing assertions (basically anything with refcheck=True) into a separate test and mark that test with @pytest.unsupported_on_pypy

@jagerman
Copy link
Member

I failed to build tests

Wait, failed to build? That part should work (with 5.7 at least).

@uentity
Copy link
Contributor Author

uentity commented Apr 20, 2017

Year, I see what you have done with disabling tests on PyPy in prev commits. I just wanted to check it myself before pushing.

But as I can see now, you build tests on PyPy 5.7 with Python 2.7 and I tried to build against PyPy 5.7 with Python 3.5. That combination gives compile error:

class_support.h:169:13: error: «struct _heaptypeobject» has no member named «ht_qualname»; did you mean «ht_name»?
  heap_type->ht_qualname = name_obj.inc_ref().ptr();
             ^~~~~~~~~~~

@wjakob
Copy link
Member

wjakob commented Apr 20, 2017

Aha, looks like some Python 3.x compatibility issues in the Python 3 version of PyPy. There is no need to worry about it here.

@jagerman
Copy link
Member

Oh, right, I forgot about the PyPy3 version. We currently only support PyPy2 5.7.

@uentity
Copy link
Contributor Author

uentity commented Apr 21, 2017

Yes, I noticed that support is only for PyPy2 5.7.
Are there any stoppers preventing this from being merged upstream?

@dean0x7d
Copy link
Member

This looks good to me. There's a merge conflict right now. Just resolve that and I think it's good to merge.

@jagerman
Copy link
Member

Can you do a rebase against master rather than a merge? It makes it easier to commit without getting merge commits in the log.

@uentity
Copy link
Contributor Author

uentity commented Apr 29, 2017

Sorry, I happened to use this slick github "resolve merge conflicts" button.

@jagerman jagerman merged commit 083a021 into pybind:master Apr 29, 2017
@jagerman
Copy link
Member

Merged! Thanks!

@dean0x7d dean0x7d modified the milestone: v2.2 Aug 13, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants