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

Add helper for parsing tuples using nanobind #3025

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

mgeplf
Copy link
Collaborator

@mgeplf mgeplf commented Aug 2, 2024

  • This can also be used for parsing args

Copy link

✔️ 72584bc -> Azure artifacts URL

Copy link

codecov bot commented Aug 2, 2024

Codecov Report

Attention: Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.

Project coverage is 67.27%. Comparing base (72f9ec4) to head (72584bc).

Files Patch % Lines
src/nrnpython/utils.hpp 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3025      +/-   ##
==========================================
- Coverage   67.27%   67.27%   -0.01%     
==========================================
  Files         572      573       +1     
  Lines      104944   104944              
==========================================
- Hits        70605    70597       -8     
- Misses      34339    34347       +8     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@bbpbuildbot

This comment has been minimized.

return NULL;
}
bool b = hoc_valid_stmt(cmd, 0);
auto [cmd] = nrn::cast_tuple<const std::string>(nb::tuple(args));
Copy link
Member

Choose a reason for hiding this comment

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

I have noticed that some of these constructors are the equivalent of the python constructor of the same name, which is also the case here. nb::tuple() calls either PySequence_Tuple or PyTuple_New effectively behaving as python's tuple()
I have manually steal or borrow and let the object be moved
(nb::tuple(nb::borrow(args)) in this case). Not sure if there is anything to be gained, it's mostly for info.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure I follow what you mean by:

I have manually steal or borrow and let the object be moved (nb::tuple(nb::borrow(args)) in this case)

ie: what you mean by moved in the above, and why would one add the extra nb::borrow()?

Copy link
Member

@ferdonline ferdonline Aug 12, 2024

Choose a reason for hiding this comment

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

My comment is basically, there's a lot going on in the background when you do nb::tuple(), whereas if you would have done nb::tuple(nb::borrow(args) there's an explicit creation of nb::object with a borrowed reference, which being an rvalue-ref it's moved to be a nb::tuple in an almost-no-op.
Since I don't fully know what your direct call implicitly does, sometimes being explicit is better. But if you find out it does the very right (and minimal) thing I'd be glad to know.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, I was on vacation and missed this.

whereas if you would have done nb::tuple(nb::borrow(args) there's an explicit creation of nb::object with a borrowed reference

I'm all for being explicit, but I'm not sure why I want to nb::borrow; the refcnt doesn't need to be incremented from what I can tell, why would we need to do that?

Copy link
Member

@ferdonline ferdonline Sep 23, 2024

Choose a reason for hiding this comment

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

nb::tuple extends nb::object, so there will be decref at the end of its life. I believe in this case we must borrow, not steal. Hence my comment, maybe we would better be explicit about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nb::tuple extends nb::object, so there will be decref at the end of its life.

I don't see why that is a problem. The nb::tuple calls https://github.com/wjakob/nanobind/blob/master/include/nanobind/nb_types.h#L478 which then calls https://github.com/wjakob/nanobind/blob/master/src/common.cpp#L623
This is then used to initialize the nb::object, so it has the correct refcount, and thus when it is dtor'd, everything is correct.

Note that both code paths go through PySequence_Tuple, so a new python tuple is created with its own lifetime and refcount.

Comment on lines 12 to 13
if (I >= t.size()) {
throw std::runtime_error("Not enough arguments");
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 we should let t[I] do its Python thing. Upon out-of-bounds it will set the Python exception and return Null, which is likely what we want
https://docs.python.org/3/c-api/tuple.html#c.PyTuple_GetItem

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it's better if we are helpful with our error messages. I think that my current exception isn't worded well, and could be better, but that's a detail I need to fill in later.

More importantly, without the check, the code will segfault. Perhaps you have a different implementation in mind?

Copy link
Member

Choose a reason for hiding this comment

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

So I guess something is confusing me. Why would t[i] segfault if it only calls PyTuple_GetItem under the hood?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We now also have access to libfmt, so you can use that to improve the error message.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have updated the error message in the newest version.

@@ -0,0 +1,31 @@
#pragma once
Copy link
Collaborator

Choose a reason for hiding this comment

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

The file can be called nrnpython/cast_tuple.hpp.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

Copy link
Collaborator

@1uc 1uc left a comment

Choose a reason for hiding this comment

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

Minor improvements, w.r.t. naming/error messages would be nice. Other than that I think it'll provide a useful building block for modernizing a particular pattern found frequently in our code.

} // namespace detail

template <typename... Ts, typename Tuple>
auto cast_tuple(Tuple&& tuple) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why any Tuple, if we know it's a const nanobind::tuple&?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was running into an issue trying to handle the nb::args case, and that's how I ended up working around it.
I have added a test if you want to try/suggest alternatives.

@bbpbuildbot

This comment has been minimized.

@bbpbuildbot

This comment has been minimized.

@bbpbuildbot

This comment has been minimized.

Copy link

✔️ 63e575a -> Azure artifacts URL

Copy link

sonarcloud bot commented Aug 21, 2024

Copy link

✔️ 738ff1c -> Azure artifacts URL

@mgeplf
Copy link
Collaborator Author

mgeplf commented Aug 22, 2024

@alkino / @1uc I seem to be getting errors with nanobind not being available:
https://github.com/neuronsimulator/nrn/actions/runs/10488896362/job/29052319171#step:18:2088

I'm confused as to why the tests are "automatically" including nanobind:
/usr/bin/clang++-18 -DCORENRN_BUILD=0 -DHAVE_CONFIG_H -DMPICH_SKIP_MPICXX=1 -DMPI_NO_CPPBIND=1 -DNRN_ENABLE_THREADS -DOMPI_SKIP_MPICXX=1 -DUSE_PYTHON -I/home/gevaert/tmp/nrn/src/ivoc [...] -isystem /home/gevaert/tmp/nrn/external/nanobind/include -ferror-limit=100000 -openmp-simd -g -O0 -std=c++17 -Werror=return-type -Wno-deprecated-non-prototype -pthread -o test/CMakeFiles/testneuron.dir/unit_tests/utils/cast_tuple.cpp.o -c /home/gevaert/tmp/nrn/test/unit_tests/utils/cast_tuple.cpp",
without it being specified for the tests?

My plan to deal with it is to only include the cast_tuple.cpp test when it's being compiled with python.
Give that, is there a better place to put the tests?

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.

4 participants