diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index afadc106..6d71529c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -123,7 +123,7 @@ jobs: - name: Install packages run: | apt update - apt-get -y install python3-dev python3-venv python3-pybind11 ninja-build + apt-get -y install python3-dev python3-venv nanobind-dev ninja-build - name: Clone w/ submodules uses: actions/checkout@v3 diff --git a/.github/workflows/test_pip.yml b/.github/workflows/test_pip.yml index a014f70e..bf928f5e 100644 --- a/.github/workflows/test_pip.yml +++ b/.github/workflows/test_pip.yml @@ -47,7 +47,7 @@ jobs: - name: Install python run: | apt update - apt-get -y install python3-dev python3-venv python3-pybind11 + apt-get -y install python3-dev python3-venv - name: Clone w/ submodules uses: actions/checkout@v3 diff --git a/README.md b/README.md index 0b832647..c9e8e8cd 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Documentation and instructions at [GHEX Documentation](https://ghex-org.github.i - gfortran compiler (optional, for Fortran bindings) - Python3 (optional, for Python bindings ) - MPI4PY (optional, for Python bindings ) -- Pybind11 (optional, for Python bindings) +- Nanobind (optional, for Python bindings) ### From Source diff --git a/bindings/python/src/_pyghex/CMakeLists.txt b/bindings/python/src/_pyghex/CMakeLists.txt index 7f52ee9f..00c9030f 100644 --- a/bindings/python/src/_pyghex/CMakeLists.txt +++ b/bindings/python/src/_pyghex/CMakeLists.txt @@ -1,3 +1,5 @@ +set(CMAKE_CXX_STANDARD 17) + set(pyghex_source config.cpp context_shim.cpp @@ -25,7 +27,6 @@ set_property(TARGET pyghex_obj PROPERTY POSITION_INDEPENDENT_CODE ON) target_include_directories(pyghex_obj PRIVATE $ ) -target_link_libraries(pyghex_obj PRIVATE pybind11::module) ghex_target_compile_options(pyghex_obj) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") target_compile_options(pyghex_obj PRIVATE -fbracket-depth=512) @@ -40,18 +41,16 @@ compile_as_cuda(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} SOURCES ${pyghex_source}) # ================== # Create the Python module in the build directory. # The module contains the dynamic library, __init__.py and VERSION information. -add_library(pyghex MODULE $) +nanobind_add_module(pyghex $) +target_link_libraries(pyghex_obj PRIVATE nanobind-static) # With this, the full name of the library will be something like: # _pyghex.cpython-36m-x86_64-linux-gnu.so set_target_properties(pyghex PROPERTIES OUTPUT_NAME _pyghex - PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}" # Choose this particular output directory for testing purposes LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../../ghex") # This dependency has to be spelt out again, despite being added to # pyghex_obj because CMake. -target_link_libraries(pyghex PRIVATE pybind11::module) target_link_libraries(pyghex PRIVATE ghex) ghex_link_to_oomph(ghex) diff --git a/bindings/python/src/_pyghex/config.cpp b/bindings/python/src/_pyghex/config.cpp index 2e725c72..49ec4f8f 100644 --- a/bindings/python/src/_pyghex/config.cpp +++ b/bindings/python/src/_pyghex/config.cpp @@ -11,8 +11,8 @@ #include #include -#include -#include +#include +#include #include @@ -22,38 +22,37 @@ namespace pyghex // Returns a python dictionary that python users can use to look up // which options the GHEX library was configured with at compile time. -pybind11::dict +nanobind::dict config() { #define mk_str(x) mk_tok(x) #define mk_tok(x) #x - pybind11::dict dict; + nanobind::dict dict; - dict[pybind11::str("transport")] = pybind11::str(mk_str(GHEX_TRANSPORT_BACKEND)); + dict[nanobind::str("transport")] = nanobind::str(mk_str(GHEX_TRANSPORT_BACKEND)); #ifdef GHEX_USE_GPU - dict[pybind11::str("gpu")] = pybind11::bool_(true); + dict[nanobind::str("gpu")] = nanobind::bool_(true); #else - dict[pybind11::str("gpu")] = pybind11::bool_(false); + dict[nanobind::str("gpu")] = nanobind::bool_(false); #endif - dict[pybind11::str("gpu_mode")] = pybind11::str(mk_str(GHEX_GPU_MODE)); + dict[nanobind::str("gpu_mode")] = nanobind::str(mk_str(GHEX_GPU_MODE)); #ifdef GHEX_USE_XPMEM - dict[pybind11::str("xpmem")] = pybind11::bool_(true); + dict[nanobind::str("xpmem")] = nanobind::bool_(true); #else - dict[pybind11::str("xpmem")] = pybind11::bool_(false); + dict[nanobind::str("xpmem")] = nanobind::bool_(false); #endif #define mk_ver(M, m, p) mk_tok(M) "." mk_tok(m) "." mk_tok(p) { const char* version = mk_ver(GHEX_VERSION_MAJOR, GHEX_VERSION_MINOR, GHEX_VERSION_PATCH); - dict[pybind11::str("version")] = pybind11::str(version); + dict[nanobind::str("version")] = nanobind::str(version); } { - const char* version = - mk_ver(PYBIND11_VERSION_MAJOR, PYBIND11_VERSION_MINOR, PYBIND11_VERSION_PATCH); - dict[pybind11::str("pybind-version")] = pybind11::str(version); + const char* version = mk_ver(NB_VERSION_MAJOR, NB_VERSION_MINOR, NB_VERSION_PATCH); + dict[nanobind::str("nanobind-version")] = nanobind::str(version); } #undef mk_str #undef mk_ver @@ -63,27 +62,27 @@ config() } void -print_config(const pybind11::dict& d) +print_config(const nanobind::dict& d) { std::stringstream s; s << "GHEX's configuration:\n"; for (auto x : d) { - s << " " << std::left << std::setw(16) << x.first << ": " << std::right << std::setw(10) - << x.second << "\n"; + s << " " << std::left << std::setw(16) << nanobind::cast(x.first) << ": " + << std::right << std::setw(10) << nanobind::str(x.second).c_str() << "\n"; } - pybind11::print(s.str()); + nanobind::print(s.str().c_str()); } // Register configuration void -register_config(pybind11::module& m) +register_config(nanobind::module_& m) { m.def("config", &config, "Get GHEX's configuration.") .def( - "print_config", [](const pybind11::dict& d) { return print_config(d); }, + "print_config", [](const nanobind::dict& d) { return print_config(d); }, "Print GHEX's configuration."); } } // namespace pyghex diff --git a/bindings/python/src/_pyghex/context_shim.cpp b/bindings/python/src/_pyghex/context_shim.cpp index 7c8db752..031221a5 100644 --- a/bindings/python/src/_pyghex/context_shim.cpp +++ b/bindings/python/src/_pyghex/context_shim.cpp @@ -8,7 +8,8 @@ * SPDX-License-Identifier: BSD-3-Clause */ #include -#include +#include +#include #include #include @@ -35,13 +36,13 @@ to_string(const context_shim& c) } // namespace util void -register_context(pybind11::module& m) +register_context(nanobind::module_& m) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; - pybind11::class_(m, "context") - .def(pybind11::init(), "mpi_comm"_a, "thread_safe"_a = false, + nanobind::class_(m, "context") + .def(nanobind::init(), "mpi_comm"_a, "thread_safe"_a = false, "Create a ghex context") .def("__str__", util::to_string) .def("__repr__", util::to_string) diff --git a/bindings/python/src/_pyghex/module.cpp b/bindings/python/src/_pyghex/module.cpp index 5729291e..e9bf5d8d 100644 --- a/bindings/python/src/_pyghex/module.cpp +++ b/bindings/python/src/_pyghex/module.cpp @@ -7,43 +7,41 @@ * Please, refer to the LICENSE file in the root directory. * SPDX-License-Identifier: BSD-3-Clause */ -#include - -namespace py = pybind11; +#include namespace pyghex { -void register_config(pybind11::module& m); -void register_mpi(pybind11::module& m); -void register_context(pybind11::module& m); -void register_py_dtype_to_cpp_name(pybind11::module& m); +void register_config(nanobind::module_& m); +void register_mpi(nanobind::module_& m); +void register_context(nanobind::module_& m); +void register_py_dtype_to_cpp_name(nanobind::module_& m); namespace structured { namespace regular { -void register_domain_descriptor(pybind11::module& m); -void register_halo_generator(pybind11::module& m); -void register_field_descriptor(pybind11::module& m); -void register_pattern(pybind11::module& m); -void register_communication_object(pybind11::module& m); +void register_domain_descriptor(nanobind::module_& m); +void register_halo_generator(nanobind::module_& m); +void register_field_descriptor(nanobind::module_& m); +void register_pattern(nanobind::module_& m); +void register_communication_object(nanobind::module_& m); } // namespace regular } // namespace structured namespace unstructured { -void register_domain_descriptor(pybind11::module& m); -void register_halo_generator(pybind11::module& m); -void register_field_descriptor(pybind11::module& m); -void register_pattern(pybind11::module& m); -void register_communication_object(pybind11::module& m); +void register_domain_descriptor(nanobind::module_& m); +void register_halo_generator(nanobind::module_& m); +void register_field_descriptor(nanobind::module_& m); +void register_pattern(nanobind::module_& m); +void register_communication_object(nanobind::module_& m); } // namespace unstructured } // namespace pyghex -PYBIND11_MODULE(_pyghex, m) +NB_MODULE(_pyghex, m) { - m.doc() = "pybind11 ghex bindings"; // optional module docstring + m.doc() = "nanobind ghex bindings"; pyghex::register_config(m); pyghex::register_mpi(m); diff --git a/bindings/python/src/_pyghex/mpi_comm_shim.cpp b/bindings/python/src/_pyghex/mpi_comm_shim.cpp index 73d94c77..6dcd3b2c 100644 --- a/bindings/python/src/_pyghex/mpi_comm_shim.cpp +++ b/bindings/python/src/_pyghex/mpi_comm_shim.cpp @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include @@ -30,11 +32,11 @@ namespace // Convert a Python object to an MPI Communicator. inline MPI_Comm -convert_to_mpi_comm(pybind11::object o) +convert_to_mpi_comm(nanobind::object o) { - if (!pybind11::hasattr(o, "py2f")) - throw pybind11::type_error("Argument must be `mpi4py.MPI.Comm`"); - return MPI_Comm_f2c(o.attr("py2f")().cast()); + if (!nanobind::hasattr(o, "py2f")) + throw nanobind::type_error("Argument must be `mpi4py.MPI.Comm`"); + return MPI_Comm_f2c(nanobind::cast(o.attr("py2f")())); } } // anonymous namespace @@ -55,7 +57,7 @@ to_string(const mpi_comm_shim& c) } } // namespace util -mpi_comm_shim::mpi_comm_shim(pybind11::object o) { comm = convert_to_mpi_comm(o); } +mpi_comm_shim::mpi_comm_shim(nanobind::object o) { comm = convert_to_mpi_comm(o); } void mpi_init(bool threadsafe) @@ -93,15 +95,14 @@ mpi_is_finalized() } void -register_mpi(pybind11::module& m) +register_mpi(nanobind::module_& m) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; - pybind11::class_ mpi_comm(m, "mpi_comm"); - mpi_comm.def(pybind11::init<>()) - .def(pybind11::init([](pybind11::object o) { return mpi_comm_shim(o); }), "mpi_comm_obj"_a, - "MPI communicator object.") + nanobind::class_ mpi_comm(m, "mpi_comm"); + mpi_comm.def(nanobind::init<>()) + .def(nanobind::init(), "mpi_comm_obj"_a, "MPI communicator object.") .def("__str__", util::to_string) .def("__repr__", util::to_string); diff --git a/bindings/python/src/_pyghex/mpi_comm_shim.hpp b/bindings/python/src/_pyghex/mpi_comm_shim.hpp index 67541d61..ccaeb6da 100644 --- a/bindings/python/src/_pyghex/mpi_comm_shim.hpp +++ b/bindings/python/src/_pyghex/mpi_comm_shim.hpp @@ -10,7 +10,7 @@ #pragma once #include -#include +#include #include namespace pyghex @@ -19,7 +19,7 @@ namespace pyghex // This is a slight variation of Arbor's mpi_comm_shim as introduced here // https://github.com/arbor-sim/arbor/commit/1d6a48d0ce4b96f59acf931efd61d55c571c5e68 // A shim is required for MPI_Comm, because OpenMPI defines it as a pointer to -// a forward-declared type, which pybind11 won't allow as an argument. +// a forward-declared type, which nanobind won't allow as an argument. // MPICH and its derivatives use an integer. struct mpi_comm_shim { @@ -30,7 +30,7 @@ struct mpi_comm_shim : comm(c) { } - mpi_comm_shim(pybind11::object o); + mpi_comm_shim(nanobind::object o); }; namespace util diff --git a/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp b/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp index 18e1e6d0..96838a5a 100644 --- a/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp +++ b/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp @@ -9,7 +9,9 @@ */ #include -#include +#include +#include +#include #include #include @@ -17,22 +19,49 @@ #include #include -namespace py = pybind11; - namespace pyghex { +namespace +{ +nanobind::object +numpy_dtype(const nanobind::handle& value) +{ + return nanobind::module_::import_("numpy").attr("dtype")( + nanobind::borrow(value)); +} + +template +nanobind::object +dtype_of() +{ + if constexpr (std::is_same_v) return numpy_dtype(nanobind::str("float64")); + else if constexpr (std::is_same_v) + return numpy_dtype(nanobind::str("float32")); + else if constexpr (std::is_same_v) + return numpy_dtype(nanobind::str("bool")); + else if constexpr (std::is_same_v) + return numpy_dtype(nanobind::str("int32")); + else if constexpr (std::is_same_v) + return numpy_dtype(nanobind::str("int64")); + else + static_assert(sizeof(T) == 0, "unsupported dtype"); +} +} // namespace + std::string -py_dtype_to_cpp_name(py::dtype dtype) +py_dtype_to_cpp_name(nanobind::handle dtype) { + const auto canonical_dtype = numpy_dtype(dtype); std::string cpp_name; gridtools::for_each( - [&cpp_name, &dtype](auto l) + [&cpp_name, &canonical_dtype](auto l) { using type = decltype(l); - if (dtype.is(py::dtype::of())) + auto candidate_dtype = dtype_of(); + if (nanobind::bool_(canonical_dtype.equal(candidate_dtype))) { assert(cpp_name.empty()); cpp_name = util::mangle_python(); @@ -45,7 +74,7 @@ py_dtype_to_cpp_name(py::dtype dtype) } void -register_py_dtype_to_cpp_name(pybind11::module& m) +register_py_dtype_to_cpp_name(nanobind::module_& m) { m.def("py_dtype_to_cpp_name", &py_dtype_to_cpp_name, "Convert numpy dtype to C++ type name"); } diff --git a/bindings/python/src/_pyghex/register_class.hpp b/bindings/python/src/_pyghex/register_class.hpp index d33f669e..2f43ac74 100644 --- a/bindings/python/src/_pyghex/register_class.hpp +++ b/bindings/python/src/_pyghex/register_class.hpp @@ -11,21 +11,21 @@ #include -#include -#include +#include +#include namespace pyghex { template auto -register_class(pybind11::module& m) +register_class(nanobind::module_& m) { auto demangled = util::demangle(); auto pymangled = util::mangle_python(demangled); - return pybind11::class_(m, pymangled.c_str()) - .def_property_readonly_static("__cpp_type__", - [demangled](const pybind11::object&) { return demangled; }) + return nanobind::class_(m, pymangled.c_str()) + .def_prop_ro_static("__cpp_type__", + [demangled](const nanobind::object&) { return demangled; }) .def("__str__", [pymangled](const T&) { return ""; }) .def("__repr__", [pymangled](const T&) { return ""; }); } diff --git a/bindings/python/src/_pyghex/structured/regular/communication_object.cpp b/bindings/python/src/_pyghex/structured/regular/communication_object.cpp index d46d2de1..bb24c832 100644 --- a/bindings/python/src/_pyghex/structured/regular/communication_object.cpp +++ b/bindings/python/src/_pyghex/structured/regular/communication_object.cpp @@ -17,6 +17,10 @@ #include #include +#include +#include +#include + namespace pyghex { namespace structured @@ -25,7 +29,7 @@ namespace regular { void -register_communication_object(pybind11::module& m) +register_communication_object(nanobind::module_& m) { auto _communication_object = register_class(m); @@ -34,7 +38,7 @@ register_communication_object(pybind11::module& m) [&m, &_communication_object](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using communication_object_type = gridtools::meta::first; using handle_type = typename communication_object_type::handle_type; using pattern_type = typename communication_object_type::pattern_type; @@ -59,28 +63,28 @@ register_communication_object(pybind11::module& m) "exchange", [](communication_object_shim& co, std::vector b) { return co.exchange(b.begin(), b.end()); }, - pybind11::keep_alive<0, 1>()) + nanobind::keep_alive<0, 1>()) .def( "exchange", [](communication_object_shim& co, buffer_info_type& b) - { return co.exchange(b); }, pybind11::keep_alive<0, 1>()) + { return co.exchange(b); }, nanobind::keep_alive<0, 1>()) .def( "exchange", [](communication_object_shim& co, buffer_info_type& b0, buffer_info_type& b1) { return co.exchange(b0, b1); }, - pybind11::keep_alive<0, 1>()) + nanobind::keep_alive<0, 1>()) .def( "exchange", [](communication_object_shim& co, buffer_info_type& b0, buffer_info_type& b1, buffer_info_type& b2) { return co.exchange(b0, b1, b2); }, - pybind11::keep_alive<0, 1>()); + nanobind::keep_alive<0, 1>()); }); }); m.def( "make_co_regular", [](context_shim& c) { return communication_object_shim{&c.m, std::monostate{}}; }, - pybind11::keep_alive<0, 1>()); + nanobind::keep_alive<0, 1>()); } } //namespace regular diff --git a/bindings/python/src/_pyghex/structured/regular/domain_descriptor.cpp b/bindings/python/src/_pyghex/structured/regular/domain_descriptor.cpp index fafdf6c5..f635cbe6 100644 --- a/bindings/python/src/_pyghex/structured/regular/domain_descriptor.cpp +++ b/bindings/python/src/_pyghex/structured/regular/domain_descriptor.cpp @@ -16,6 +16,11 @@ #include #include +#include +#include +#include +#include + namespace pyghex { namespace structured @@ -40,14 +45,14 @@ as_tuple(const std::array& arr) } // namespace void -register_domain_descriptor(pybind11::module& m) +register_domain_descriptor(nanobind::module_& m) { gridtools::for_each< gridtools::meta::transform>( [&m](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using type = gridtools::meta::first; using domain_id_type = typename type::domain_id_type; @@ -57,7 +62,7 @@ register_domain_descriptor(pybind11::module& m) auto _domain_descriptor = register_class(m); _domain_descriptor - .def(pybind11::init(), "domain_id"_a, "first"_a, + .def(nanobind::init(), "domain_id"_a, "first"_a, "last"_a, "Create a domain descriptor") .def("domain_id", &type::domain_id, "Returns the domain id") .def( diff --git a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp index e38664b0..953eb872 100644 --- a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp @@ -24,6 +24,11 @@ #include #include +#include +#include +#include +#include + namespace pyghex { namespace structured @@ -36,90 +41,44 @@ template using int_tuple_constant = gridtools::meta::list...>; template -struct buffer_info_accessor -{ -}; +struct ndarray_device_type; template<> -struct buffer_info_accessor +struct ndarray_device_type { - static pybind11::buffer_info get(pybind11::object& buffer) - { - return buffer.cast().request(); - } + using type = nanobind::device::cpu; }; template<> -struct buffer_info_accessor +struct ndarray_device_type { - static pybind11::buffer_info get(pybind11::object& buffer) - { - using namespace pybind11::literals; #ifdef __HIP_PLATFORM_HCC__ - pybind11::dict info = buffer.attr("__hip_array_interface__"); + using type = nanobind::device::rocm; #else - pybind11::dict info = buffer.attr("__cuda_array_interface__"); + using type = nanobind::device::cuda; #endif - - [[maybe_unused]] bool readonly = info["data"].cast()[1].cast(); - assert(!readonly); - - void* ptr = reinterpret_cast( - info["data"].cast()[0].cast()); - - // create buffer protocol format and itemsize from typestr - pybind11::function memory_view = pybind11::module::import("builtins").attr("memoryview"); - pybind11::function np_array = pybind11::module::import("numpy").attr("array"); - pybind11::buffer empty_buffer = - memory_view(np_array(pybind11::list(), "dtype"_a = info["typestr"])); - pybind11::ssize_t itemsize = empty_buffer.request().itemsize; - std::string format = empty_buffer.request().format; - - std::vector shape = info["shape"].cast>(); - pybind11::ssize_t ndim = shape.size(); - - std::vector strides(ndim); - if (pybind11::isinstance(info["strides"])) - { - strides[ndim - 1] = 1; - for (int i = ndim - 2; i >= 0; --i) - { - strides[i] = (strides[i + 1] * shape[i + 1]) * itemsize; - } - } - else - { - strides = info["strides"].cast>(); - assert(pybind11::ssize_t(strides.size()) == ndim); - } - - return pybind11::buffer_info(ptr, /* Pointer to buffer */ - itemsize, /* Size of one scalar */ - format, /* Python struct-style format descriptor */ - ndim, /* Number of dimensions */ - shape, /* Buffer dimensions */ - strides /* Strides (in bytes) for each index */ - ); - } }; -template -pybind11::buffer_info -get_buffer_info(pybind11::object& buffer) +template +std::vector +byte_strides(const T& b, std::ptrdiff_t itemsize) { - return buffer_info_accessor::get(buffer); + std::vector result(b.ndim()); + for (size_t i = 0; i < b.ndim(); ++i) result[i] = b.stride(i) * itemsize; + return result; } + } // namespace void -register_field_descriptor(pybind11::module& m) +register_field_descriptor(nanobind::module_& m) { gridtools::for_each< gridtools::meta::transform>( [&m](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using field_descriptor_type = gridtools::meta::first; using T = typename field_descriptor_type::value_type; @@ -135,43 +94,54 @@ register_field_descriptor(pybind11::module& m) ghex::buffer_info; auto _field_descriptor = register_class(m); - /*auto _buffer_info =*/register_class(m); + register_class(m); - _field_descriptor.def( - pybind11::init( - [](const domain_descriptor_type& dom, pybind11::object& b, const array& offsets, - const array& extents) + auto make_field_descriptor = + [](const domain_descriptor_type& dom, + nanobind::ndarray::type> b, + const array& offsets, const array& extents) + { + if (b.ndim() != dimension::value) + { + std::stringstream error; + error << "Field has wrong dimensions. Expected " << dimension::value + << ", but got " << b.ndim(); + throw nanobind::type_error(error.str().c_str()); + } + + auto strides = byte_strides(b, sizeof(T)); + + auto ordered_strides = strides; + std::sort(ordered_strides.begin(), ordered_strides.end(), + [](std::ptrdiff_t a, std::ptrdiff_t b) { return a > b; }); + + array b_layout_map; + for (size_t i = 0; i < dimension::value; ++i) + { + auto it = std::find(ordered_strides.begin(), ordered_strides.end(), strides[i]); + b_layout_map[i] = std::distance(ordered_strides.begin(), it); + if (b_layout_map[i] != layout_map::at(i)) { - pybind11::buffer_info info = get_buffer_info(b); - - if (!info.item_type_is_equivalent_to()) - { - std::stringstream error; - error << "Incompatible format: expected a " << typeid(T).name() - << " buffer."; - throw pybind11::type_error(error.str()); - } - - auto ordered_strides = info.strides; - std::sort(ordered_strides.begin(), ordered_strides.end(), - [](int a, int b) { return a > b; }); - array b_layout_map; - for (size_t i = 0; i < dimension::value; ++i) - { - auto it = std::find(ordered_strides.begin(), ordered_strides.end(), - info.strides[i]); - b_layout_map[i] = std::distance(ordered_strides.begin(), it); - if (b_layout_map[i] != layout_map::at(i)) - { - throw pybind11::type_error( - "Buffer has a different layout than specified."); - } - } - - return ghex::wrap_field(dom, - static_cast(info.ptr), offsets, extents, info.strides); - }), - pybind11::keep_alive<0, 2>()); + throw nanobind::type_error("Buffer has a different layout than specified."); + } + } + + return ghex::wrap_field(dom, static_cast(b.data()), + offsets, extents, strides); + }; + +#if NB_VERSION_MAJOR < 2 + _field_descriptor.def( + "__init__", + [make_field_descriptor](field_descriptor_type* t, const domain_descriptor_type& dom, + nanobind::ndarray::type> b, + const array& offsets, const array& extents) + { new (t) field_descriptor_type(make_field_descriptor(dom, b, offsets, extents)); }, + nanobind::keep_alive<1, 3>()); +#else + _field_descriptor.def(nanobind::new_(make_field_descriptor), + nanobind::keep_alive<0, 3>()); +#endif }); } diff --git a/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp b/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp index a54e6851..d99d62c8 100644 --- a/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp +++ b/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp @@ -13,6 +13,11 @@ #include #include +#include +#include +#include +#include + namespace pyghex { namespace structured @@ -21,14 +26,14 @@ namespace regular { void -register_halo_generator(pybind11::module& m) +register_halo_generator(nanobind::module_& m) { gridtools::for_each< gridtools::meta::transform>( [&m](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using type = gridtools::meta::first; using dimension = typename type::dimension; @@ -43,23 +48,27 @@ register_halo_generator(pybind11::module& m) auto _box2 = register_class(m); _halo_generator - .def(pybind11::init(), "first"_a, + .def(nanobind::init(), "first"_a, "last"_a, "halos"_a, "periodic"_a, "Create a halo generator") - .def("__call__", &type::operator()); + .def("__call__", + [](const type& halo_gen, const typename type::domain_type& domain) + { + nanobind::list result; + for (const auto& halo : halo_gen(domain)) + result.append(nanobind::cast(halo)); + return result; + }); - _box2 - .def_property_readonly("local", - pybind11::overload_cast<>(&box2::local, pybind11::const_)) - .def_property_readonly("global_", - pybind11::overload_cast<>(&box2::global, pybind11::const_)); + _box2.def_prop_ro("local", [](const box2& b) { return b.local(); }) + .def_prop_ro("global_", [](const box2& b) { return b.global(); }); - _box.def_property_readonly("first", + _box.def_prop_ro("first", [](const box& b) { auto first = b.first(); return static_cast(first); }) - .def_property_readonly("last", + .def_prop_ro("last", [](const box& b) { auto last = b.last(); diff --git a/bindings/python/src/_pyghex/structured/regular/pattern.cpp b/bindings/python/src/_pyghex/structured/regular/pattern.cpp index 629b870b..cd2cdcff 100644 --- a/bindings/python/src/_pyghex/structured/regular/pattern.cpp +++ b/bindings/python/src/_pyghex/structured/regular/pattern.cpp @@ -17,6 +17,10 @@ #include #include +#include +#include +#include + namespace pyghex { namespace structured @@ -25,14 +29,14 @@ namespace regular { void -register_pattern(pybind11::module& m) +register_pattern(nanobind::module_& m) { gridtools::for_each< gridtools::meta::transform>( [&m](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using type = gridtools::meta::first; using halo_gen = typename type::halo_gen; @@ -48,14 +52,14 @@ register_pattern(pybind11::module& m) auto _pattern_container = register_class(m); _pattern_container - .def_property_readonly_static("grid_type", [](const pybind11::object&) + .def_prop_ro_static("grid_type", [](const nanobind::object&) { return util::mangle_python(); }) - .def_property_readonly_static("domain_id_type", [](const pybind11::object&) + .def_prop_ro_static("domain_id_type", [](const nanobind::object&) { return util::mangle_python(); }); m.def( "make_pattern_regular", [](context_shim& c, halo_gen& h, domain_range& d) - { return ghex::make_pattern(c.m, h, d); }, pybind11::keep_alive<0, 1>()); + { return ghex::make_pattern(c.m, h, d); }, nanobind::keep_alive<0, 1>()); gridtools::for_each>( [&m, &_pattern_container](auto k) @@ -66,7 +70,7 @@ register_pattern(pybind11::module& m) // "identifier undefined in device code" error when using NVCC _pattern_container.def( "__call__", [](const pattern_container& pattern, field& f) - { return pattern(f); }, pybind11::keep_alive<0, 2>()); + { return pattern(f); }, nanobind::keep_alive<0, 2>()); }); }); } diff --git a/bindings/python/src/_pyghex/unstructured/communication_object.cpp b/bindings/python/src/_pyghex/unstructured/communication_object.cpp index 66de9143..ffb74687 100644 --- a/bindings/python/src/_pyghex/unstructured/communication_object.cpp +++ b/bindings/python/src/_pyghex/unstructured/communication_object.cpp @@ -24,6 +24,10 @@ #include #include +#include +#include +#include + namespace pyghex { namespace unstructured @@ -32,7 +36,7 @@ namespace { #if defined(GHEX_CUDACC) cudaStream_t -extract_cuda_stream(pybind11::object python_stream) +extract_cuda_stream(nanobind::object python_stream) { static_assert(std::is_pointer::value); if (python_stream.is_none()) @@ -42,56 +46,56 @@ extract_cuda_stream(pybind11::object python_stream) } else { - if (pybind11::hasattr(python_stream, "__cuda_stream__")) + if (nanobind::hasattr(python_stream, "__cuda_stream__")) { // CUDA stream protocol: https://nvidia.github.io/cuda-python/cuda-core/latest/interoperability.html#cuda-stream-protocol - pybind11::tuple cuda_stream_protocol = - pybind11::getattr(python_stream, "__cuda_stream__")(); + nanobind::tuple cuda_stream_protocol = + nanobind::cast(python_stream.attr("__cuda_stream__")()); if (cuda_stream_protocol.size() != 2) { std::stringstream error; error << "Expected a tuple of length 2, but got one with length " << cuda_stream_protocol.size(); - throw pybind11::type_error(error.str()); + throw nanobind::type_error(error.str().c_str()); } - //Currently there is only version 0. - const auto protocol_version = cuda_stream_protocol[0].cast(); + const auto protocol_version = nanobind::cast(cuda_stream_protocol[0]); if (protocol_version != 0) { std::stringstream error; error << "Expected `__cuda_stream__` protocol version 0, but got " << protocol_version; - throw pybind11::type_error(error.str()); + throw nanobind::type_error(error.str().c_str()); } - const auto stream_address = cuda_stream_protocol[1].cast(); + const auto stream_address = nanobind::cast(cuda_stream_protocol[1]); return reinterpret_cast(stream_address); } - else if (pybind11::hasattr(python_stream, "ptr")) + else if (nanobind::hasattr(python_stream, "ptr")) { // CuPy stream: See https://docs.cupy.dev/en/latest/reference/generated/cupy.cuda.Stream.html#cupy-cuda-stream - std::uintptr_t stream_address = python_stream.attr("ptr").cast(); + std::uintptr_t stream_address = + nanobind::cast(python_stream.attr("ptr")); return reinterpret_cast(stream_address); } // TODO: Find out of how to extract the typename, i.e. `type(python_stream).__name__`. std::stringstream error; error << "Failed to convert the stream object into a CUDA stream."; - throw pybind11::type_error(error.str()); + throw nanobind::type_error(error.str().c_str()); } } #endif } // namespace void -register_communication_object(pybind11::module& m) +register_communication_object(nanobind::module_& m) { gridtools::for_each< gridtools::meta::transform>( [&m](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using type = gridtools::meta::first; using handle = typename type::handle_type; @@ -106,9 +110,9 @@ register_communication_object(pybind11::module& m) #if defined(GHEX_CUDACC) .def( "schedule_wait", - [](typename type::handle_type& h, pybind11::object python_stream) + [](typename type::handle_type& h, nanobind::object python_stream) { return h.schedule_wait(extract_cuda_stream(python_stream)); }, - pybind11::keep_alive<0, 1>()) + nanobind::keep_alive<0, 1>()) #endif .def("is_ready", &handle::is_ready) .def("progress", &handle::progress); @@ -124,52 +128,52 @@ register_communication_object(pybind11::module& m) .def( "exchange", [](type& co, std::vector b) { return co.exchange(b.begin(), b.end()); }, - pybind11::keep_alive<0, 1>()) + nanobind::keep_alive<0, 1>()) .def( "exchange", [](type& co, buffer_info_type& b) - { return co.exchange(b); }, pybind11::keep_alive<0, 1>()) + { return co.exchange(b); }, nanobind::keep_alive<0, 1>()) .def( "exchange", [](type& co, buffer_info_type& b0, buffer_info_type& b1) - { return co.exchange(b0, b1); }, pybind11::keep_alive<0, 1>()) + { return co.exchange(b0, b1); }, nanobind::keep_alive<0, 1>()) .def( "exchange", [](type& co, buffer_info_type& b0, buffer_info_type& b1, buffer_info_type& b2) { return co.exchange(b0, b1, b2); }, - pybind11::keep_alive<0, 1>()) + nanobind::keep_alive<0, 1>()) #if defined(GHEX_CUDACC) .def( "schedule_exchange", - [](type& co, pybind11::object python_stream, + [](type& co, nanobind::object python_stream, std::vector b) { return co.schedule_exchange(extract_cuda_stream(python_stream), b.begin(), b.end()); }, - pybind11::keep_alive<0, 1>(), pybind11::arg("stream"), - pybind11::arg("patterns")) + nanobind::keep_alive<0, 1>(), nanobind::arg("stream"), + nanobind::arg("patterns")) .def( "schedule_exchange", - [](type& co, pybind11::object python_stream, buffer_info_type& b) + [](type& co, nanobind::object python_stream, buffer_info_type& b) { return co.schedule_exchange(extract_cuda_stream(python_stream), b); }, - pybind11::keep_alive<0, 1>(), pybind11::arg("stream"), - pybind11::arg("b")) + nanobind::keep_alive<0, 1>(), nanobind::arg("stream"), + nanobind::arg("b")) .def( "schedule_exchange", - [](type& co, pybind11::object python_stream, buffer_info_type& b0, + [](type& co, nanobind::object python_stream, buffer_info_type& b0, buffer_info_type& b1) { return co.schedule_exchange(extract_cuda_stream(python_stream), b0, b1); }, - pybind11::keep_alive<0, 1>(), pybind11::arg("stream"), - pybind11::arg("b0"), pybind11::arg("b1")) + nanobind::keep_alive<0, 1>(), nanobind::arg("stream"), + nanobind::arg("b0"), nanobind::arg("b1")) .def( "schedule_exchange", - [](type& co, pybind11::object python_stream, buffer_info_type& b0, + [](type& co, nanobind::object python_stream, buffer_info_type& b0, buffer_info_type& b1, buffer_info_type& b2) { return co.schedule_exchange(extract_cuda_stream(python_stream), b0, b1, b2); }, - pybind11::keep_alive<0, 1>(), pybind11::arg("stream"), - pybind11::arg("b0"), pybind11::arg("b1"), pybind11::arg("b2")) + nanobind::keep_alive<0, 1>(), nanobind::arg("stream"), + nanobind::arg("b0"), nanobind::arg("b1"), nanobind::arg("b2")) .def("has_scheduled_exchange", [](type& co) -> bool { return co.has_scheduled_exchange(); }) #endif // end scheduled exchange @@ -178,7 +182,7 @@ register_communication_object(pybind11::module& m) m.def( "make_co_unstructured", [](context_shim& c) { return type{c.m}; }, - pybind11::keep_alive<0, 1>()); + nanobind::keep_alive<0, 1>()); m.def("expose_cpp_ptr", [](type* obj) { return reinterpret_cast(obj); }); diff --git a/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp b/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp index 2f6af561..d353dda4 100644 --- a/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp +++ b/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp @@ -15,19 +15,23 @@ #include #include +#include +#include +#include + namespace pyghex { namespace unstructured { void -register_domain_descriptor(pybind11::module& m) +register_domain_descriptor(nanobind::module_& m) { gridtools::for_each< gridtools::meta::transform>( [&m](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using type = gridtools::meta::first; using domain_id_type = typename type::domain_id_type; @@ -37,12 +41,21 @@ register_domain_descriptor(pybind11::module& m) auto _domain_descriptor = register_class(m); _domain_descriptor - .def(pybind11::init( +#if NB_VERSION_MAJOR < 2 + .def("__init__", + [](type* t, domain_id_type id, const std::vector& gids, + const std::vector& halo_lids) { + new (t) + type{id, gids.begin(), gids.end(), halo_lids.begin(), halo_lids.end()}; + }) +#else + .def(nanobind::new_( [](domain_id_type id, const std::vector& gids, const std::vector& halo_lids) { return type{id, gids.begin(), gids.end(), halo_lids.begin(), halo_lids.end()}; })) +#endif .def("domain_id", &type::domain_id, "Returns the domain id") .def("size", &type::size, "Returns the size") .def("inner_size", &type::inner_size, "Returns the inner size") diff --git a/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp b/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp index 4685aa30..790853db 100644 --- a/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp @@ -19,97 +19,47 @@ #include #include +#include +#include +#include + namespace pyghex { namespace unstructured { + namespace { - template -struct buffer_info_accessor -{ -}; +struct ndarray_device_type; template<> -struct buffer_info_accessor +struct ndarray_device_type { - static pybind11::buffer_info get(pybind11::object& buffer) - { - return buffer.cast().request(); - } + using type = nanobind::device::cpu; }; template<> -struct buffer_info_accessor +struct ndarray_device_type { - static pybind11::buffer_info get(pybind11::object& buffer) - { - using namespace pybind11::literals; #ifdef __HIP_PLATFORM_HCC__ - pybind11::dict info = buffer.attr("__hip_array_interface__"); + using type = nanobind::device::rocm; #else - pybind11::dict info = buffer.attr("__cuda_array_interface__"); + using type = nanobind::device::cuda; #endif - - [[maybe_unused]] bool readonly = info["data"].cast()[1].cast(); - assert(!readonly); - - void* ptr = reinterpret_cast( - info["data"].cast()[0].cast()); - - // Create buffer protocol format and itemsize from typestr - pybind11::function memory_view = pybind11::module::import("builtins").attr("memoryview"); - pybind11::function np_array = pybind11::module::import("numpy").attr("array"); - pybind11::buffer empty_buffer = - memory_view(np_array(pybind11::list(), "dtype"_a = info["typestr"])); - pybind11::ssize_t itemsize = empty_buffer.request().itemsize; - std::string format = empty_buffer.request().format; - - std::vector shape = info["shape"].cast>(); - pybind11::ssize_t ndim = shape.size(); - - std::vector strides(ndim); - if (pybind11::isinstance(info["strides"])) - { - // If `strides` field is `None` then it is contiguous C-style, - // see https://numpy.org/devdocs/reference/arrays.interface.html - strides[ndim - 1] = itemsize; - for (int i = ndim - 2; i >= 0; --i) { strides[i] = strides[i + 1] * shape[i + 1]; } - } - else - { - strides = info["strides"].cast>(); - assert(pybind11::ssize_t(strides.size()) == ndim); - } - - return pybind11::buffer_info(ptr, /* Pointer to buffer */ - itemsize, /* Size of one scalar */ - format, /* Python struct-style format descriptor */ - ndim, /* Number of dimensions */ - shape, /* Buffer dimensions */ - strides /* Strides (in bytes) for each index */ - ); - } }; -template -pybind11::buffer_info -get_buffer_info(pybind11::object& buffer) -{ - return buffer_info_accessor::get(buffer); -} } // namespace void -register_field_descriptor(pybind11::module& m) +register_field_descriptor(nanobind::module_& m) { gridtools::for_each< gridtools::meta::transform>( [&m](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using type = gridtools::meta::first; using T = typename type::value_type; @@ -121,107 +71,94 @@ register_field_descriptor(pybind11::module& m) using buffer_info_type = ghex::buffer_info; auto _field_descriptor = register_class(m); - /*auto _buffer_info = */ register_class(m); - - _field_descriptor.def( - pybind11::init( - [](const domain_descriptor_type& dom, pybind11::object& b) + register_class(m); + + auto make_field_descriptor = + [](const domain_descriptor_type& dom, + nanobind::ndarray::type> b) + { + if (b.ndim() > 2u) + { + std::stringstream error; + error << "Field has too many dimensions. Expected at most 2, but got " + << b.ndim(); + throw nanobind::type_error(error.str().c_str()); + } + + if (static_cast(b.shape(0)) != dom.size()) + { + std::stringstream error; + error << "Field's first dimension (" << static_cast(b.shape(0)) + << ") must match the size of the domain (" << dom.size() << ")"; + throw nanobind::type_error(error.str().c_str()); + } + + bool levels_first = true; + std::size_t outer_strides = 0u; + const auto stride_0 = b.stride(0) * sizeof(T); + const auto stride_1 = + (b.ndim() == 2) ? (b.stride(1) * sizeof(T)) : std::ptrdiff_t{0}; + if (b.ndim() == 2 && stride_1 != sizeof(T)) + { + levels_first = false; + if (stride_0 != sizeof(T)) { - pybind11::buffer_info info = get_buffer_info(b); - - if (!info.item_type_is_equivalent_to()) - { - std::stringstream error; - error << "Incompatible format: expected a " << typeid(T).name() - << " buffer."; - throw pybind11::type_error(error.str()); - } - - if (info.ndim > 2u) - { - std::stringstream error; - error << "Field has too many dimensions. Expected at most 2, but got " - << info.ndim; - throw pybind11::type_error(error.str()); - } - - if (static_cast(info.shape[0]) != dom.size()) - { - std::stringstream error; - error << "Field's first dimension (" - << static_cast(info.shape[0]) - << ") must match the size of the domain (" << dom.size() << ")"; - throw pybind11::type_error(error.str()); - } - - // NOTE: In `buffer_info` the strides are in bytes, but in - // GHEX they are in elements. - bool levels_first = true; - std::size_t outer_strides = 0u; - if (info.ndim == 2 && info.strides[1] != sizeof(T)) - { - levels_first = false; - if (info.strides[0] != sizeof(T)) - { - std::stringstream error; - error << "Field's strides are not compatible with GHEX. Expected " - "that the (byte) stride of dimension 0 is " - << sizeof(T) << " but got " << (std::size_t)(info.strides[0]) - << "."; - throw pybind11::type_error(error.str()); - } - if (((std::size_t)(info.strides[1]) % sizeof(T)) != 0) - { - std::stringstream error; - error << "Field's strides are not compatible with GHEX. Expected " - "that the (byte) stride of dimension 1 " - << (std::size_t)(info.strides[1]) - << " is a multiple of the element size " << sizeof(T) << "."; - throw pybind11::type_error(error.str()); - } - outer_strides = info.strides[1] / sizeof(T); - } - else if (info.ndim == 2) - { - if (info.strides[1] != sizeof(T)) - { - std::stringstream error; - error << "Field's strides are not compatible with GHEX. Expected " - "that the (byte) stride of dimension 1 is " - << sizeof(T) << " but got " << (std::size_t)(info.strides[1]) - << "."; - throw pybind11::type_error(error.str()); - } - if (((std::size_t)(info.strides[0]) % sizeof(T)) != 0) - { - std::stringstream error; - error << "Field's strides are not compatible with GHEX. Expected " - "that the (byte) stride of dimension 0 " - << (std::size_t)(info.strides[0]) - << " is a multiple of the element size of " << sizeof(T) - << "."; - throw pybind11::type_error(error.str()); - } - outer_strides = info.strides[0] / sizeof(T); - } - else - { - // Note this case only happens for `info.ndim == 1`. - if (info.strides[0] != sizeof(T)) - { - std::stringstream error; - error << "Field's strides are not compatible with GHEX. With one " - " dimension expected the stride to be " - << sizeof(T) << " but got " << info.strides[0] << "."; - throw pybind11::type_error(error.str()); - } - } - std::size_t levels = (info.ndim == 1) ? 1u : (std::size_t)info.shape[1]; - - return type{dom, static_cast(info.ptr), levels, levels_first, - outer_strides}; - }), - pybind11::keep_alive<0, 2>()); + std::stringstream error; + error << "Field's strides are not compatible with GHEX. Expected that the " + "(byte) stride of dimension 0 is " + << sizeof(T) << " but got " << (std::size_t)(stride_0) << "."; + throw nanobind::type_error(error.str().c_str()); + } + if (((std::size_t)(stride_1) % sizeof(T)) != 0) + { + std::stringstream error; + error << "Field's strides are not compatible with GHEX. Expected that the " + "(byte) stride of dimension 1 " + << (std::size_t)(stride_1) << " is a multiple of the element size " + << sizeof(T) << "."; + throw nanobind::type_error(error.str().c_str()); + } + outer_strides = stride_1 / sizeof(T); + } + else if (b.ndim() == 2) + { + // stride_1 == sizeof(T): levels are the inner (fast) dimension + if (((std::size_t)(stride_0) % sizeof(T)) != 0) + { + std::stringstream error; + error << "Field's strides are not compatible with GHEX. Expected that the " + "(byte) stride of dimension 0 " + << (std::size_t)(stride_0) << " is a multiple of the element size of " + << sizeof(T) << "."; + throw nanobind::type_error(error.str().c_str()); + } + outer_strides = stride_0 / sizeof(T); + } + else if (stride_0 != sizeof(T)) + { + std::stringstream error; + error + << "Field's strides are not compatible with GHEX. With one dimension expected " + "the stride to be " + << sizeof(T) << " but got " << stride_0 << "."; + throw nanobind::type_error(error.str().c_str()); + } + + const std::size_t levels = (b.ndim() == 1) ? 1u : (std::size_t)b.shape(1); + return type{dom, static_cast(b.data()), levels, levels_first, outer_strides}; + }; + +#if NB_VERSION_MAJOR < 2 + _field_descriptor.def( + "__init__", + [make_field_descriptor](type* t, const domain_descriptor_type& dom, + nanobind::ndarray::type> b) + { new (t) type(make_field_descriptor(dom, b)); }, + nanobind::keep_alive<1, 3>()); +#else + _field_descriptor.def(nanobind::new_(make_field_descriptor), + nanobind::keep_alive<0, 3>()); +#endif }); } diff --git a/bindings/python/src/_pyghex/unstructured/halo_generator.cpp b/bindings/python/src/_pyghex/unstructured/halo_generator.cpp index f505c958..dc543289 100644 --- a/bindings/python/src/_pyghex/unstructured/halo_generator.cpp +++ b/bindings/python/src/_pyghex/unstructured/halo_generator.cpp @@ -12,19 +12,23 @@ #include #include +#include +#include +#include + namespace pyghex { namespace unstructured { void -register_halo_generator(pybind11::module& m) +register_halo_generator(nanobind::module_& m) { gridtools::for_each< gridtools::meta::transform>( [&m](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using type = gridtools::meta::first; using global_index_type = typename type::global_index_type; @@ -33,9 +37,8 @@ register_halo_generator(pybind11::module& m) auto _halo_generator = register_class(m); /*auto _halo = */ register_class(m); - _halo_generator.def(pybind11::init<>(), "Create a halo generator") - .def(pybind11::init( - [](const std::vector& gids) { return type{gids}; })) + _halo_generator.def(nanobind::init<>(), "Create a halo generator") + .def(nanobind::init>()) .def("__call__", &type::operator()); }); } diff --git a/bindings/python/src/_pyghex/unstructured/pattern.cpp b/bindings/python/src/_pyghex/unstructured/pattern.cpp index 01f1ae12..15a0acc7 100644 --- a/bindings/python/src/_pyghex/unstructured/pattern.cpp +++ b/bindings/python/src/_pyghex/unstructured/pattern.cpp @@ -19,20 +19,24 @@ #include #include +#include +#include +#include + namespace pyghex { namespace unstructured { void -register_pattern(pybind11::module& m) +register_pattern(nanobind::module_& m) { gridtools::for_each< gridtools::meta::transform>( [&m](auto l) { using namespace std::string_literals; - using namespace pybind11::literals; + using namespace nanobind::literals; using type = gridtools::meta::first; using halo_gen = typename type::halo_gen; @@ -46,14 +50,14 @@ register_pattern(pybind11::module& m) auto _pattern_container = register_class(m); _pattern_container - .def_property_readonly_static("grid_type", [](const pybind11::object&) + .def_prop_ro_static("grid_type", [](const nanobind::object&) { return util::mangle_python(); }) - .def_property_readonly_static("domain_id_type", [](const pybind11::object&) + .def_prop_ro_static("domain_id_type", [](const nanobind::object&) { return util::mangle_python(); }); m.def( "make_pattern_unstructured", [](context_shim& c, halo_gen& h, domain_range& d) - { return ghex::make_pattern(c.m, h, d); }, pybind11::keep_alive<0, 1>()); + { return ghex::make_pattern(c.m, h, d); }, nanobind::keep_alive<0, 1>()); gridtools::for_each>( [&m, &_pattern_container](auto k) @@ -64,7 +68,7 @@ register_pattern(pybind11::module& m) // "identifier undefined in device code" error when using NVCC _pattern_container.def( "__call__", [](const pattern_container& pattern, field& f) - { return pattern(f); }, pybind11::keep_alive<0, 2>()); + { return pattern(f); }, nanobind::keep_alive<0, 2>()); }); m.def("expose_cpp_ptr", diff --git a/bindings/python/src/ghex/CMakeLists.txt b/bindings/python/src/ghex/CMakeLists.txt index 599c5af4..495619e1 100644 --- a/bindings/python/src/ghex/CMakeLists.txt +++ b/bindings/python/src/ghex/CMakeLists.txt @@ -8,10 +8,13 @@ else() endif() if(GHEX_WITH_TESTING) - add_custom_target(pyghex_files) - add_custom_command(TARGET pyghex_files PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/../../ghex") - add_custom_command(TARGET pyghex_files PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/version.txt "${CMAKE_CURRENT_BINARY_DIR}/../../ghex") - add_dependencies(pyghex pyghex_files) + file(GLOB_RECURSE pyghex_python_files CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.py") + + add_custom_target(pyghex_files ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/../../ghex" + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/version.txt "${CMAKE_CURRENT_BINARY_DIR}/../../ghex" + DEPENDS ${pyghex_python_files} ${CMAKE_BINARY_DIR}/version.txt + COMMENT "Refreshing Python binding files for tests") + + add_dependencies(pyghex_files pyghex) endif() diff --git a/bindings/python/src/ghex/pyghex/__init__.py b/bindings/python/src/ghex/pyghex/__init__.py index 994e2636..8d2f5ff4 100644 --- a/bindings/python/src/ghex/pyghex/__init__.py +++ b/bindings/python/src/ghex/pyghex/__init__.py @@ -8,7 +8,7 @@ # SPDX-License-Identifier: BSD-3-Clause # -# The Python wrapper generated using pybind11 is a compiled dynamic library, +# The Python wrapper generated using nanobind is a compiled dynamic library, # with a name like _pyghex.cpython-38-x86_64-linux-gnu.so # # The library will be installed in the same path as this file, which will diff --git a/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index d3424636..41fe9fed 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -1,17 +1,14 @@ include(GNUInstallDirs) -set(PYBIND11_CPP_STANDARD -std=c++17) - if (GHEX_BUILD_PYTHON_BINDINGS) - find_package (Python3 REQUIRED COMPONENTS Interpreter Development.Module) + find_package(Python 3 REQUIRED COMPONENTS Interpreter Development.Module) - if(${Python3_FOUND}) - set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}") - endif() + set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") include(ghex_find_python_module) - find_package(pybind11 REQUIRED PATHS ${Python_SITELIB}) + find_python_module(nanobind REQUIRED) + find_package(nanobind CONFIG REQUIRED PATHS "${PY_NANOBIND}/cmake") # Ask Python where it keeps its system (platform) packages. file(WRITE "${CMAKE_BINARY_DIR}/install-prefix" "${CMAKE_INSTALL_PREFIX}") diff --git a/include/ghex/structured/regular/halo_generator.hpp b/include/ghex/structured/regular/halo_generator.hpp index c8f0fc5f..9bdfbdd8 100644 --- a/include/ghex/structured/regular/halo_generator.hpp +++ b/include/ghex/structured/regular/halo_generator.hpp @@ -9,6 +9,8 @@ */ #pragma once +#include + #include #include #include diff --git a/pyproject.toml b/pyproject.toml index 0294fca5..63395f00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,8 @@ build-backend = 'scikit_build_core.build' requires = [ 'cmake', - 'pybind11>=2.6', - 'scikit-build-core', - 'wheel', + 'nanobind>=1.0.0', + 'scikit-build-core' ] [project] diff --git a/test/bindings/python/CMakeLists.txt b/test/bindings/python/CMakeLists.txt index 2069165a..c48ea5c9 100644 --- a/test/bindings/python/CMakeLists.txt +++ b/test/bindings/python/CMakeLists.txt @@ -16,7 +16,7 @@ get_filename_component(pyghex_test_workdir ${python_mod_path}/.. ABSOLUTE) # command to create a virtual environment add_custom_command( OUTPUT ${venv_dir} - COMMAND ${Python3_EXECUTABLE} -m venv ${venv_dir} + COMMAND ${Python_EXECUTABLE} -m venv ${venv_dir} COMMENT "Creating virtual environment for test dependencies" ) @@ -48,9 +48,10 @@ add_custom_target( # setup test target # ================= -add_custom_target(pyghex_tests) -add_dependencies(pyghex pyghex_tests) -add_dependencies(setup_test_env pyghex_tests) +add_custom_target(pyghex_tests ALL) +add_dependencies(pyghex_tests pyghex) +add_dependencies(pyghex_tests pyghex_files) +add_dependencies(pyghex_tests setup_test_env) copy_files(TARGET pyghex_tests DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/fixtures FILES ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/context.py)