Skip to content

Use pybind11 for tri module #24522

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

Merged
merged 6 commits into from
Dec 6, 2022
Merged

Conversation

ianthomas23
Copy link
Member

This is a pybind11 wrapping of the _tri module. It is taken from the pybind11 proof of concept (#23787) with some of @anntzer's extra comments addressed. Also see pybind transition plan #23846.

Worthy of note for future PRs that wrap C/C++ code using pybind11 is how I have chosen to deal with passing None in lieu of NumPy arrays from Python to C++ (i.e. when the array is optional). These are auto converted to arrays using the target dtype. So for float and bool arrays this is fine but it doesn't work for integer arrays

>>> np.array(None, dtype=int)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'

Hence passing from a Python Triangulation to a C++ one the various optional array arguments are passed as empty tuples rather than None. These appear on the C++ side as arrays with size==0 and ndim==1. Probably when not passing optional integers arrays I would prefer to stick to None, which appear as arrays with size==1 and ndim==0, but rather than mix the two approaches in the same function I have just used the tuple approach. All of this is on the internal C++ API behind the Python Triangulation so it doesn't affect user code either way.

Copy link
Member

@oscargus oscargus left a comment

Choose a reason for hiding this comment

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

Seems to make sense to me, but I probably trust the passing tests more than I trust my knowledge in the area...

py::arg("neighbors"),
py::arg("correct_triangle_orientations"),
"Create a new C++ Triangulation object.\n"
"This should not be called directly, instead use the python class\n"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"This should not be called directly, instead use the python class\n"
"This should not be called directly, use the python class\n"

(or drop the instead on the next line.)

py::arg("triangulation"),
py::arg("z"),
"Create a new C++ TriContourGenerator object.\n"
"This should not be called directly, instead use the functions\n"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"This should not be called directly, instead use the functions\n"
"This should not be called directly, use the functions\n"

Same

.def(py::init<Triangulation&>(),
py::arg("triangulation"),
"Create a new C++ TrapezoidMapTriFinder object.\n"
"This should not be called directly, instead use the python class\n"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"This should not be called directly, instead use the python class\n"
"This should not be called directly, use the python class\n"

mpl._tri.Triangulation()

with pytest.raises(
ValueError, match=r'x and y must be 1D arrays of the same length'):
mpl._tri.Triangulation([], [1], [[]], None, None, None, False)
mpl._tri.Triangulation([], [1], [[]], (), (), (), False)
Copy link
Member

Choose a reason for hiding this comment

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

Because of lacking knowledge: will this cause problems for anyone possibly creating the Triangulation explicitly? Am I correct assuming that passing None is no longer feasible at this level?

Copy link
Member

Choose a reason for hiding this comment

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

In principle yes, but we do not expose this class out publicly (there is a Python-side Triangulation class in tri._triangulation that we re-export in tri/__init__.py).

I think it is OK both that we change this API without warning and that we have tests that touch private API.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not entirely happy with this internal conversion of None to empty tuple for optional integer arrays but I cannot think of an alternative that isn't worse. In the long run we will use std::optional but that is C++17. I wouldn't want to use any C++17 until SciPy does, but that may not be far away (https://docs.scipy.org/doc/scipy/dev/toolchain.html#c-language-standards).

In the meantime we can use std::optional by shipping our own optional.h but I wouldn't want to do that as if there is a problem with some obscure compiler we will be on our own. Or we could write our own bespoke np.array | None pybind11 converter class but that is much more esoteric C++ than we currently have. So the least bad option is to stick with the None to tuple until we are OK with using C++17.

Note to self: need to add explicit internal API tests for set_mask as this also accepts an optional array.

Copy link
Contributor

Choose a reason for hiding this comment

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

fwiw scipy seems to aim to bump to c++17 in the next (1.10) release, see scipy/scipy#16589.

@tacaswell tacaswell added this to the v3.7.0 milestone Nov 28, 2022
@tacaswell
Copy link
Member

The appveyor failure is real:

 c:\users\appveyor\appdata\local\temp\1\pip-build-env-uvxxe3n3\overlay\lib\site-packages\pybind11\include\pybind11\detail/common.h(86): fatal error C1189: #error:  pybind11 2.10+ requires MSVC 2017 or newer

Currently we only require 2015, but bumping to 2017 seems resonable to me.

@ianthomas23 ianthomas23 force-pushed the pybind11_tri_module branch 2 times, most recently from 2f8a3b0 to 7baffa4 Compare December 2, 2022 10:47
@ianthomas23
Copy link
Member Author

All checks are passing now that I have changed the appveyor image to Visual Studio 2017.

I've also had to preinstall pybind11 just before the python setup.py bdist_wheel appveyor stage. This won't be needed when we upgrade the wheel building stage to python -m build or equivalent.

@oscargus oscargus added the CI: Run cibuildwheel Run wheel building tests on a PR label Dec 2, 2022
@oscargus
Copy link
Member

oscargus commented Dec 2, 2022

I added the Run cibuildwheel label just to confirm, and that seems to be passing as well.

@QuLogic
Copy link
Member

QuLogic commented Dec 3, 2022

I've also had to preinstall pybind11 just before the python setup.py bdist_wheel appveyor stage. This won't be needed when we upgrade the wheel building stage to python -m build or equivalent.

I think there's no need for this stage at all, and opened #24595 to remove it.

@ianthomas23
Copy link
Member Author

I've removed the appveyor wheel-building fix and rebased to pick up #24595.

@oscargus oscargus merged commit 046cd46 into matplotlib:main Dec 6, 2022
@ianthomas23 ianthomas23 deleted the pybind11_tri_module branch December 6, 2022 13:47
@ianthomas23 ianthomas23 mentioned this pull request Dec 6, 2022
13 tasks
timhoffm added a commit to timhoffm/matplotlib that referenced this pull request Dec 19, 2022
We need pybind11 and a C++ compiler because of matplotlib#24522
timhoffm added a commit to timhoffm/matplotlib that referenced this pull request Dec 20, 2022
We need pybind11 and a C++ compiler because of matplotlib#24522
raphaelquast pushed a commit to raphaelquast/matplotlib that referenced this pull request Mar 16, 2023
We need pybind11 and a C++ compiler because of matplotlib#24522
QuLogic added a commit to QuLogic/matplotlib that referenced this pull request Mar 9, 2024
Prior to their conversion to pybind11 classes in matplotlib#24522, these types
had `tp_flags = Py_TPFLAGS_DEFAULT`, meaning they did not have the
`Py_TPFLAGS_BASETYPE` flag and could not be subtyped. As these are
internal classes, I don't believe this was intentionally chnaged, so
restore that flag.

Additionally, since we require C++17 now, mark the C++ classes
themselves as `final`. I believe this really only makes a difference if
we have `virtual` methods (which we don't here), but that doesn't mean
the compiler can't infer some other optimizations with this information.
QuLogic added a commit to QuLogic/matplotlib that referenced this pull request Mar 9, 2024
Prior to their conversion to pybind11 classes in matplotlib#24522, these types
had `tp_flags = Py_TPFLAGS_DEFAULT`, meaning they did not have the
`Py_TPFLAGS_BASETYPE` flag and could not be subtyped. As these are
internal classes, I don't believe this was intentionally changed, so
restore that flag.

Additionally, since we require C++17 now, mark the C++ classes
themselves as `final`. I believe this really only makes a difference if
we have `virtual` methods (which we don't here), but that doesn't mean
the compiler can't infer some other optimizations with this information.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Run cibuildwheel Run wheel building tests on a PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants