From 51735b3b3f543a76821d1350702d9c1733a69acb Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Fri, 6 Mar 2026 21:54:58 +0100 Subject: [PATCH 01/38] Initial nanobind refactoring --- bindings/python/src/_pyghex/CMakeLists.txt | 10 +- bindings/python/src/_pyghex/config.cpp | 39 +++-- bindings/python/src/_pyghex/context_shim.cpp | 10 +- bindings/python/src/_pyghex/context_shim.hpp | 2 + bindings/python/src/_pyghex/module.cpp | 36 ++-- bindings/python/src/_pyghex/mpi_comm_shim.cpp | 21 +-- bindings/python/src/_pyghex/mpi_comm_shim.hpp | 4 +- .../src/_pyghex/py_dtype_to_cpp_name.cpp | 11 +- .../python/src/_pyghex/register_class.hpp | 10 +- .../regular/communication_object.cpp | 18 +- .../structured/regular/domain_descriptor.cpp | 11 +- .../structured/regular/field_descriptor.cpp | 127 ++++---------- .../structured/regular/halo_generator.cpp | 17 +- .../_pyghex/structured/regular/pattern.cpp | 15 +- .../unstructured/communication_object.cpp | 61 +++---- .../unstructured/domain_descriptor.cpp | 10 +- .../_pyghex/unstructured/field_descriptor.cpp | 160 +++++------------- .../_pyghex/unstructured/halo_generator.cpp | 12 +- .../src/_pyghex/unstructured/pattern.cpp | 15 +- pyproject.toml | 9 +- 20 files changed, 248 insertions(+), 350 deletions(-) diff --git a/bindings/python/src/_pyghex/CMakeLists.txt b/bindings/python/src/_pyghex/CMakeLists.txt index 7f52ee9fa..ae579475d 100644 --- a/bindings/python/src/_pyghex/CMakeLists.txt +++ b/bindings/python/src/_pyghex/CMakeLists.txt @@ -1,3 +1,7 @@ +set(CMAKE_CXX_STANDARD 17) + +find_package(nanobind CONFIG REQUIRED) + set(pyghex_source config.cpp context_shim.cpp @@ -25,7 +29,7 @@ set_property(TARGET pyghex_obj PROPERTY POSITION_INDEPENDENT_CODE ON) target_include_directories(pyghex_obj PRIVATE $ ) -target_link_libraries(pyghex_obj PRIVATE pybind11::module) +target_link_libraries(pyghex_obj PRIVATE nanobind::nanobind) ghex_target_compile_options(pyghex_obj) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") target_compile_options(pyghex_obj PRIVATE -fbracket-depth=512) @@ -40,7 +44,7 @@ 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 $) # 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 @@ -51,7 +55,7 @@ set_target_properties(pyghex PROPERTIES 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 nanobind::nanobind) 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 2e725c724..104f266ce 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::cast(x.second) << "\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 7c8db752d..6a1ca82b5 100644 --- a/bindings/python/src/_pyghex/context_shim.cpp +++ b/bindings/python/src/_pyghex/context_shim.cpp @@ -8,7 +8,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ #include -#include +#include #include #include @@ -35,13 +35,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/context_shim.hpp b/bindings/python/src/_pyghex/context_shim.hpp index f89b9405c..d23f94728 100644 --- a/bindings/python/src/_pyghex/context_shim.hpp +++ b/bindings/python/src/_pyghex/context_shim.hpp @@ -9,6 +9,8 @@ */ #pragma once +#include + #include #include diff --git a/bindings/python/src/_pyghex/module.cpp b/bindings/python/src/_pyghex/module.cpp index 5729291ee..dbcfee45b 100644 --- a/bindings/python/src/_pyghex/module.cpp +++ b/bindings/python/src/_pyghex/module.cpp @@ -7,43 +7,43 @@ * Please, refer to the LICENSE file in the root directory. * SPDX-License-Identifier: BSD-3-Clause */ -#include +#include -namespace py = pybind11; +namespace nb = nanobind; 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(nb::module_& m); +void register_mpi(nb::module_& m); +void register_context(nb::module_& m); +void register_py_dtype_to_cpp_name(nb::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(nb::module_& m); +void register_halo_generator(nb::module_& m); +void register_field_descriptor(nb::module_& m); +void register_pattern(nb::module_& m); +void register_communication_object(nb::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(nb::module_& m); +void register_halo_generator(nb::module_& m); +void register_field_descriptor(nb::module_& m); +void register_pattern(nb::module_& m); +void register_communication_object(nb::module_& m); } // namespace unstructured } // namespace pyghex -PYBIND11_MODULE(_pyghex, m) +NB_MODULE(_pyghex, m) { - m.doc() = "pybind11 ghex bindings"; // optional module docstring + // m.doc() = "pybind11 ghex bindings"; // optional module docstring 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 73d94c773..1957cdcb8 100644 --- a/bindings/python/src/_pyghex/mpi_comm_shim.cpp +++ b/bindings/python/src/_pyghex/mpi_comm_shim.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -30,11 +31,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 +56,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,14 +94,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, + nanobind::class_ mpi_comm(m, "mpi_comm"); + mpi_comm.def(nanobind::init<>()) + .def(nanobind::init([](nanobind::object o) { return mpi_comm_shim(o); }), "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 67541d617..1c9fe90b4 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 @@ -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 18e1e6d0d..2b43aec00 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,8 @@ */ #include -#include +#include +#include #include #include @@ -17,13 +18,13 @@ #include #include -namespace py = pybind11; +namespace nb = nanobind; namespace pyghex { std::string -py_dtype_to_cpp_name(py::dtype dtype) +py_dtype_to_cpp_name(nb::dtype dtype) { std::string cpp_name; @@ -32,7 +33,7 @@ py_dtype_to_cpp_name(py::dtype dtype) { using type = decltype(l); - if (dtype.is(py::dtype::of())) + if (dtype == nb::dtype::of()) { assert(cpp_name.empty()); cpp_name = util::mangle_python(); @@ -45,7 +46,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 d33f669ec..f6800f516 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()) + return nanobind::class_(m, pymangled.c_str()) .def_property_readonly_static("__cpp_type__", - [demangled](const pybind11::object&) { return demangled; }) + [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 d46d2de19..bb24c832e 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 fafdf6c50..f635cbe6a 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 e38664b06..7534b36ec 100644 --- a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp @@ -24,6 +24,10 @@ #include #include +#include +#include +#include + namespace pyghex { namespace structured @@ -35,91 +39,17 @@ namespace template using int_tuple_constant = gridtools::meta::list...>; -template -struct buffer_info_accessor -{ -}; - -template<> -struct buffer_info_accessor -{ - static pybind11::buffer_info get(pybind11::object& buffer) - { - return buffer.cast().request(); - } -}; - -template<> -struct buffer_info_accessor -{ - static pybind11::buffer_info get(pybind11::object& buffer) - { - using namespace pybind11::literals; -#ifdef __HIP_PLATFORM_HCC__ - pybind11::dict info = buffer.attr("__hip_array_interface__"); -#else - pybind11::dict info = buffer.attr("__cuda_array_interface__"); -#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) -{ - 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 field_descriptor_type = gridtools::meta::first; using T = typename field_descriptor_type::value_type; @@ -135,43 +65,58 @@ 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, + nanobind::init( + [](const domain_descriptor_type& dom, + nanobind::ndarray b, const array& offsets, const array& extents) { - pybind11::buffer_info info = get_buffer_info(b); - - if (!info.item_type_is_equivalent_to()) + if (b.ndim() != dimension::value) { std::stringstream error; - error << "Incompatible format: expected a " << typeid(T).name() - << " buffer."; - throw pybind11::type_error(error.str()); + error << "Field has wrong dimensions. Expected " << dimension::value + << ", but got " << b.ndim(); + throw nanobind::type_error(error.str().c_str()); } - auto ordered_strides = info.strides; + std::vector strides(dimension::value); + for (size_t i = 0; i < dimension::value; ++i) strides[i] = b.stride(i); + + auto ordered_strides = strides; std::sort(ordered_strides.begin(), ordered_strides.end(), - [](int a, int b) { return a > b; }); + [](nanobind::ssize_t a, nanobind::ssize_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(), - info.strides[i]); + 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( + throw nanobind::type_error( "Buffer has a different layout than specified."); } } + // GHEX expects strides in bytes? No, the original code comments in unstructured said: + // "NOTE: In `buffer_info` the strides are in bytes, but in GHEX they are in elements." + // However, let's check `ghex::wrap_field`. + // It usually takes strides in bytes or elements depending on implementation. + // The original code passed `info.strides` to `wrap_field`. `info.strides` from pybind11 buffer_info are in BYTES. + // Wait, let's check the deleted code in `structured/regular/field_descriptor.cpp`. + // It calls: `ghex::wrap_field(..., info.strides);` + // `info.strides` in `pybind11::buffer_info` are in bytes. + // So I should pass strides in bytes. + // `b.stride(i)` is in bytes. + // But `ghex::wrap_field` expects a vector of strides. + return ghex::wrap_field(dom, - static_cast(info.ptr), offsets, extents, info.strides); + static_cast(b.data()), offsets, extents, strides); }), - pybind11::keep_alive<0, 2>()); + nanobind::keep_alive<0, 2>()); }); } diff --git a/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp b/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp index a54e68515..7ba15d62c 100644 --- a/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp +++ b/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp @@ -13,6 +13,10 @@ #include #include +#include +#include +#include + namespace pyghex { namespace structured @@ -21,14 +25,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,15 +47,12 @@ 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()); - _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_property_readonly("local", [](const box2& b) { return b.local(); }) + .def_property_readonly("global_", [](const box2& b) { return b.global(); }); _box.def_property_readonly("first", [](const box& b) diff --git a/bindings/python/src/_pyghex/structured/regular/pattern.cpp b/bindings/python/src/_pyghex/structured/regular/pattern.cpp index 629b870b3..7eb06d6f5 100644 --- a/bindings/python/src/_pyghex/structured/regular/pattern.cpp +++ b/bindings/python/src/_pyghex/structured/regular/pattern.cpp @@ -17,6 +17,9 @@ #include #include +#include +#include + namespace pyghex { namespace structured @@ -25,14 +28,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 +51,14 @@ register_pattern(pybind11::module& m) auto _pattern_container = register_class(m); _pattern_container - .def_property_readonly_static("grid_type", [](const pybind11::object&) + .def_property_readonly_static("grid_type", [](const nanobind::object&) { return util::mangle_python(); }) - .def_property_readonly_static("domain_id_type", [](const pybind11::object&) + .def_property_readonly_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 +69,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 fe624d0eb..73f213d95 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,17 +46,16 @@ 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 = 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()); } const auto protocol_version = cuda_stream_protocol[0].cast(); @@ -61,13 +64,13 @@ extract_cuda_stream(pybind11::object python_stream) 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(); 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(); @@ -76,21 +79,21 @@ extract_cuda_stream(pybind11::object python_stream) // 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; @@ -105,9 +108,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); @@ -123,52 +126,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 @@ -177,7 +180,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 2f6af561b..4f6623235 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,7 +41,7 @@ register_domain_descriptor(pybind11::module& m) auto _domain_descriptor = register_class(m); _domain_descriptor - .def(pybind11::init( + .def(nanobind::init( [](domain_id_type id, const std::vector& gids, const std::vector& halo_lids) { return type{id, gids.begin(), gids.end(), halo_lids.begin(), diff --git a/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp b/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp index 4685aa30c..5ab376a92 100644 --- a/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp @@ -19,97 +19,24 @@ #include #include +#include +#include +#include + namespace pyghex { namespace unstructured { -namespace -{ - -template -struct buffer_info_accessor -{ -}; - -template<> -struct buffer_info_accessor -{ - static pybind11::buffer_info get(pybind11::object& buffer) - { - return buffer.cast().request(); - } -}; - -template<> -struct buffer_info_accessor -{ - static pybind11::buffer_info get(pybind11::object& buffer) - { - using namespace pybind11::literals; -#ifdef __HIP_PLATFORM_HCC__ - pybind11::dict info = buffer.attr("__hip_array_interface__"); -#else - pybind11::dict info = buffer.attr("__cuda_array_interface__"); -#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 +48,98 @@ 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); + register_class(m); _field_descriptor.def( - pybind11::init( - [](const domain_descriptor_type& dom, pybind11::object& b) + nanobind::init( + [](const domain_descriptor_type& dom, + nanobind::ndarray b) { - 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) + if (b.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()); + << b.ndim(); + throw nanobind::type_error(error.str().c_str()); } - if (static_cast(info.shape[0]) != dom.size()) + if (static_cast(b.shape(0)) != dom.size()) { std::stringstream error; error << "Field's first dimension (" - << static_cast(info.shape[0]) + << static_cast(b.shape(0)) << ") must match the size of the domain (" << dom.size() << ")"; - throw pybind11::type_error(error.str()); + throw nanobind::type_error(error.str().c_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)) + if (b.ndim() == 2 && b.stride(1) != sizeof(T)) { levels_first = false; - if (info.strides[0] != sizeof(T)) + if (b.stride(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]) + << sizeof(T) << " but got " << (std::size_t)(b.stride(0)) << "."; - throw pybind11::type_error(error.str()); + throw nanobind::type_error(error.str().c_str()); } - if (((std::size_t)(info.strides[1]) % sizeof(T)) != 0) + if (((std::size_t)(b.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)(info.strides[1]) + << (std::size_t)(b.stride(1)) << " is a multiple of the element size " << sizeof(T) << "."; - throw pybind11::type_error(error.str()); + throw nanobind::type_error(error.str().c_str()); } - outer_strides = info.strides[1] / sizeof(T); + outer_strides = b.stride(1) / sizeof(T); } - else if (info.ndim == 2) + else if (b.ndim() == 2) { - if (info.strides[1] != sizeof(T)) + if (b.stride(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]) + << sizeof(T) << " but got " << (std::size_t)(b.stride(1)) << "."; - throw pybind11::type_error(error.str()); + throw nanobind::type_error(error.str().c_str()); } - if (((std::size_t)(info.strides[0]) % sizeof(T)) != 0) + if (((std::size_t)(b.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)(info.strides[0]) + << (std::size_t)(b.stride(0)) << " is a multiple of the element size of " << sizeof(T) << "."; - throw pybind11::type_error(error.str()); + throw nanobind::type_error(error.str().c_str()); } - outer_strides = info.strides[0] / sizeof(T); + outer_strides = b.stride(0) / sizeof(T); } else { // Note this case only happens for `info.ndim == 1`. - if (info.strides[0] != sizeof(T)) + if (b.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 " << info.strides[0] << "."; - throw pybind11::type_error(error.str()); - } + << sizeof(T) << " but got " << b.stride(0) << "."; + throw nanobind::type_error(error.str().c_str()); + }; } - std::size_t levels = (info.ndim == 1) ? 1u : (std::size_t)info.shape[1]; + std::size_t levels = (b.ndim() == 1) ? 1u : (std::size_t)b.shape(1); - return type{dom, static_cast(info.ptr), levels, levels_first, + return type{dom, static_cast(b.data()), levels, levels_first, outer_strides}; }), - pybind11::keep_alive<0, 2>()); + nanobind::keep_alive<0, 2>()); }); } diff --git a/bindings/python/src/_pyghex/unstructured/halo_generator.cpp b/bindings/python/src/_pyghex/unstructured/halo_generator.cpp index f505c9586..14b7b2152 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,8 +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( + _halo_generator.def(nanobind::init<>(), "Create a halo generator") + .def(nanobind::init( [](const std::vector& gids) { return type{gids}; })) .def("__call__", &type::operator()); }); diff --git a/bindings/python/src/_pyghex/unstructured/pattern.cpp b/bindings/python/src/_pyghex/unstructured/pattern.cpp index 01f1ae12a..41350ea77 100644 --- a/bindings/python/src/_pyghex/unstructured/pattern.cpp +++ b/bindings/python/src/_pyghex/unstructured/pattern.cpp @@ -19,20 +19,23 @@ #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 +49,14 @@ register_pattern(pybind11::module& m) auto _pattern_container = register_class(m); _pattern_container - .def_property_readonly_static("grid_type", [](const pybind11::object&) + .def_property_readonly_static("grid_type", [](const nanobind::object&) { return util::mangle_python(); }) - .def_property_readonly_static("domain_id_type", [](const pybind11::object&) + .def_property_readonly_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 +67,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/pyproject.toml b/pyproject.toml index 5989be00a..a54494657 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,11 @@ [build-system] build-backend = 'scikit_build_core.build' requires = [ - 'cmake', - 'pybind11>=2.6', - 'scikit-build-core', - 'wheel', + "setuptools>=42", + "scikit-build>=0.13", + "cmake>=3.12", + "ninja", + "nanobind>=2.0.0" ] [project] From fb8c55403c9196370bd204188f58f168ad32d266 Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Tue, 17 Mar 2026 18:15:02 +0100 Subject: [PATCH 02/38] Some fixes --- .github/workflows/CI.yml | 2 +- .github/workflows/test_pip.yml | 2 +- README.md | 2 +- bindings/python/src/_pyghex/module.cpp | 2 +- bindings/python/src/_pyghex/mpi_comm_shim.hpp | 2 +- .../src/_pyghex/structured/regular/field_descriptor.cpp | 4 ++-- bindings/python/src/ghex/pyghex/__init__.py | 2 +- cmake/ghex_python.cmake | 4 +--- 8 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index afadc106d..18a6b401e 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 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 a014f70e2..bf928f5e1 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 0b8326476..c9e8e8cdb 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/module.cpp b/bindings/python/src/_pyghex/module.cpp index dbcfee45b..06fe9139f 100644 --- a/bindings/python/src/_pyghex/module.cpp +++ b/bindings/python/src/_pyghex/module.cpp @@ -43,7 +43,7 @@ void register_communication_object(nb::module_& m); NB_MODULE(_pyghex, m) { - // m.doc() = "pybind11 ghex bindings"; // optional module docstring + // m.doc() = "nanobind ghex bindings"; // optional module docstring pyghex::register_config(m); pyghex::register_mpi(m); diff --git a/bindings/python/src/_pyghex/mpi_comm_shim.hpp b/bindings/python/src/_pyghex/mpi_comm_shim.hpp index 1c9fe90b4..ccaeb6da7 100644 --- a/bindings/python/src/_pyghex/mpi_comm_shim.hpp +++ b/bindings/python/src/_pyghex/mpi_comm_shim.hpp @@ -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 { diff --git a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp index 7534b36ec..250a9473f 100644 --- a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp @@ -105,10 +105,10 @@ register_field_descriptor(nanobind::module_& m) // "NOTE: In `buffer_info` the strides are in bytes, but in GHEX they are in elements." // However, let's check `ghex::wrap_field`. // It usually takes strides in bytes or elements depending on implementation. - // The original code passed `info.strides` to `wrap_field`. `info.strides` from pybind11 buffer_info are in BYTES. + // The original code passed `info.strides` to `wrap_field`. `info.strides` from buffer_info are in BYTES. // Wait, let's check the deleted code in `structured/regular/field_descriptor.cpp`. // It calls: `ghex::wrap_field(..., info.strides);` - // `info.strides` in `pybind11::buffer_info` are in bytes. + // `info.strides` in buffer_info are in bytes. // So I should pass strides in bytes. // `b.stride(i)` is in bytes. // But `ghex::wrap_field` expects a vector of strides. diff --git a/bindings/python/src/ghex/pyghex/__init__.py b/bindings/python/src/ghex/pyghex/__init__.py index 994e26367..8d2f5ff4d 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 d34246364..c65e7695e 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -1,7 +1,5 @@ include(GNUInstallDirs) -set(PYBIND11_CPP_STANDARD -std=c++17) - if (GHEX_BUILD_PYTHON_BINDINGS) find_package (Python3 REQUIRED COMPONENTS Interpreter Development.Module) @@ -11,7 +9,7 @@ if (GHEX_BUILD_PYTHON_BINDINGS) endif() include(ghex_find_python_module) - find_package(pybind11 REQUIRED PATHS ${Python_SITELIB}) + # nanobind is found and linked in bindings/python/src/_pyghex/CMakeLists.txt # Ask Python where it keeps its system (platform) packages. file(WRITE "${CMAKE_BINARY_DIR}/install-prefix" "${CMAKE_INSTALL_PREFIX}") From 07f2809c74f0b3b8d943872e3bb2f195ddd21b7c Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Wed, 18 Mar 2026 14:14:50 +0100 Subject: [PATCH 03/38] More fixes --- bindings/python/src/_pyghex/CMakeLists.txt | 7 +- bindings/python/src/_pyghex/config.cpp | 3 +- bindings/python/src/_pyghex/context_shim.cpp | 1 + bindings/python/src/_pyghex/mpi_comm_shim.cpp | 4 +- .../src/_pyghex/py_dtype_to_cpp_name.cpp | 38 +++- .../python/src/_pyghex/register_class.hpp | 2 +- .../structured/regular/field_descriptor.cpp | 112 +++++----- .../structured/regular/halo_generator.cpp | 18 +- .../_pyghex/structured/regular/pattern.cpp | 5 +- .../unstructured/domain_descriptor.cpp | 13 +- .../_pyghex/unstructured/field_descriptor.cpp | 198 ++++++++++-------- .../_pyghex/unstructured/halo_generator.cpp | 8 +- .../src/_pyghex/unstructured/pattern.cpp | 5 +- bindings/python/src/ghex/CMakeLists.txt | 15 +- cmake/ghex_python.cmake | 4 +- .../structured/regular/halo_generator.hpp | 2 + pyproject.toml | 3 +- test/bindings/python/CMakeLists.txt | 8 +- 18 files changed, 266 insertions(+), 180 deletions(-) diff --git a/bindings/python/src/_pyghex/CMakeLists.txt b/bindings/python/src/_pyghex/CMakeLists.txt index ae579475d..00c9030f1 100644 --- a/bindings/python/src/_pyghex/CMakeLists.txt +++ b/bindings/python/src/_pyghex/CMakeLists.txt @@ -1,7 +1,5 @@ set(CMAKE_CXX_STANDARD 17) -find_package(nanobind CONFIG REQUIRED) - set(pyghex_source config.cpp context_shim.cpp @@ -29,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 nanobind::nanobind) ghex_target_compile_options(pyghex_obj) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") target_compile_options(pyghex_obj PRIVATE -fbracket-depth=512) @@ -45,17 +42,15 @@ 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. 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 nanobind::nanobind) 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 104f266ce..ceab6d83d 100644 --- a/bindings/python/src/_pyghex/config.cpp +++ b/bindings/python/src/_pyghex/config.cpp @@ -52,6 +52,7 @@ config() } { const char* version = mk_ver(NB_VERSION_MAJOR, NB_VERSION_MINOR, NB_VERSION_PATCH); + dict[nanobind::str("pybind-version")] = nanobind::str(version); dict[nanobind::str("nanobind-version")] = nanobind::str(version); } #undef mk_str @@ -70,7 +71,7 @@ print_config(const nanobind::dict& d) for (auto x : d) { s << " " << std::left << std::setw(16) << nanobind::cast(x.first) << ": " - << std::right << std::setw(10) << nanobind::cast(x.second) << "\n"; + << std::right << std::setw(10) << nanobind::str(x.second).c_str() << "\n"; } nanobind::print(s.str().c_str()); diff --git a/bindings/python/src/_pyghex/context_shim.cpp b/bindings/python/src/_pyghex/context_shim.cpp index 6a1ca82b5..031221a54 100644 --- a/bindings/python/src/_pyghex/context_shim.cpp +++ b/bindings/python/src/_pyghex/context_shim.cpp @@ -9,6 +9,7 @@ */ #include #include +#include #include #include diff --git a/bindings/python/src/_pyghex/mpi_comm_shim.cpp b/bindings/python/src/_pyghex/mpi_comm_shim.cpp index 1957cdcb8..6dcd3b2c9 100644 --- a/bindings/python/src/_pyghex/mpi_comm_shim.cpp +++ b/bindings/python/src/_pyghex/mpi_comm_shim.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -101,8 +102,7 @@ register_mpi(nanobind::module_& m) nanobind::class_ mpi_comm(m, "mpi_comm"); mpi_comm.def(nanobind::init<>()) - .def(nanobind::init([](nanobind::object o) { return mpi_comm_shim(o); }), "mpi_comm_obj"_a, - "MPI communicator object.") + .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/py_dtype_to_cpp_name.cpp b/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp index 2b43aec00..7bade4c6c 100644 --- a/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp +++ b/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp @@ -11,6 +11,9 @@ #include #include +#include + +#include #include #include @@ -23,17 +26,46 @@ namespace nb = nanobind; namespace pyghex { +namespace +{ +nb::object +numpy_dtype(const nb::handle& value) +{ + static nb::module_ numpy = nb::module_::import_("numpy"); + return numpy.attr("dtype")(nb::borrow(value)); +} + +template +nb::object +dtype_of() +{ + if constexpr (std::is_same_v) return numpy_dtype(nb::str("float64")); + else if constexpr (std::is_same_v) + return numpy_dtype(nb::str("float32")); + else if constexpr (std::is_same_v) + return numpy_dtype(nb::str("bool")); + else if constexpr (std::is_same_v) + return numpy_dtype(nb::str("int32")); + else if constexpr (std::is_same_v) + return numpy_dtype(nb::str("int64")); + else + static_assert(sizeof(T) == 0, "unsupported dtype"); +} +} // namespace + std::string -py_dtype_to_cpp_name(nb::dtype dtype) +py_dtype_to_cpp_name(nb::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 == nb::dtype::of()) + auto candidate_dtype = dtype_of(); + if (PyObject_RichCompareBool(canonical_dtype.ptr(), candidate_dtype.ptr(), Py_EQ) == 1) { assert(cpp_name.empty()); cpp_name = util::mangle_python(); diff --git a/bindings/python/src/_pyghex/register_class.hpp b/bindings/python/src/_pyghex/register_class.hpp index f6800f516..2f43ac747 100644 --- a/bindings/python/src/_pyghex/register_class.hpp +++ b/bindings/python/src/_pyghex/register_class.hpp @@ -24,7 +24,7 @@ register_class(nanobind::module_& m) auto demangled = util::demangle(); auto pymangled = util::mangle_python(demangled); return nanobind::class_(m, pymangled.c_str()) - .def_property_readonly_static("__cpp_type__", + .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/field_descriptor.cpp b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp index 250a9473f..2fc45640e 100644 --- a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp @@ -26,6 +26,7 @@ #include #include +#include #include namespace pyghex @@ -39,6 +40,35 @@ namespace template using int_tuple_constant = gridtools::meta::list...>; +template +struct ndarray_device_type; + +template<> +struct ndarray_device_type +{ + using type = nanobind::device::cpu; +}; + +template<> +struct ndarray_device_type +{ +#ifdef __HIP_PLATFORM_HCC__ + using type = nanobind::device::rocm; +#else + using type = nanobind::device::cuda; +#endif +}; + +template +std::vector +byte_strides(const T& b) +{ + std::vector result(b.ndim()); + const auto itemsize = static_cast(b.itemsize()); + for (size_t i = 0; i < b.ndim(); ++i) result[i] = b.stride(i) * itemsize; + return result; +} + } // namespace void @@ -67,55 +97,41 @@ register_field_descriptor(nanobind::module_& m) auto _field_descriptor = register_class(m); register_class(m); - _field_descriptor.def( - nanobind::init( - [](const domain_descriptor_type& dom, - nanobind::ndarray 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); + + auto ordered_strides = strides; + std::sort(ordered_strides.begin(), ordered_strides.end(), + [](nanobind::ssize_t a, nanobind::ssize_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)) { - 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()); - } - - std::vector strides(dimension::value); - for (size_t i = 0; i < dimension::value; ++i) strides[i] = b.stride(i); - - auto ordered_strides = strides; - std::sort(ordered_strides.begin(), ordered_strides.end(), - [](nanobind::ssize_t a, nanobind::ssize_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)) - { - throw nanobind::type_error( - "Buffer has a different layout than specified."); - } - } - - // GHEX expects strides in bytes? No, the original code comments in unstructured said: - // "NOTE: In `buffer_info` the strides are in bytes, but in GHEX they are in elements." - // However, let's check `ghex::wrap_field`. - // It usually takes strides in bytes or elements depending on implementation. - // The original code passed `info.strides` to `wrap_field`. `info.strides` from buffer_info are in BYTES. - // Wait, let's check the deleted code in `structured/regular/field_descriptor.cpp`. - // It calls: `ghex::wrap_field(..., info.strides);` - // `info.strides` in buffer_info are in bytes. - // So I should pass strides in bytes. - // `b.stride(i)` is in bytes. - // But `ghex::wrap_field` expects a vector of strides. - - return ghex::wrap_field(dom, - static_cast(b.data()), offsets, extents, strides); - }), + throw nanobind::type_error("Buffer has a different layout than specified."); + } + } + + return ghex::wrap_field(dom, static_cast(b.data()), + offsets, extents, strides); + }; + + _field_descriptor.def(nanobind::new_(make_field_descriptor), nanobind::keep_alive<0, 2>()); }); } diff --git a/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp b/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp index 7ba15d62c..d99d62c85 100644 --- a/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp +++ b/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace pyghex { @@ -49,18 +50,25 @@ register_halo_generator(nanobind::module_& m) _halo_generator .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", [](const box2& b) { return b.local(); }) - .def_property_readonly("global_", [](const box2& b) { return b.global(); }); + _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 7eb06d6f5..cd2cdcff2 100644 --- a/bindings/python/src/_pyghex/structured/regular/pattern.cpp +++ b/bindings/python/src/_pyghex/structured/regular/pattern.cpp @@ -19,6 +19,7 @@ #include #include +#include namespace pyghex { @@ -51,9 +52,9 @@ register_pattern(nanobind::module_& m) auto _pattern_container = register_class(m); _pattern_container - .def_property_readonly_static("grid_type", [](const nanobind::object&) + .def_prop_ro_static("grid_type", [](const nanobind::object&) { return util::mangle_python(); }) - .def_property_readonly_static("domain_id_type", [](const nanobind::object&) + .def_prop_ro_static("domain_id_type", [](const nanobind::object&) { return util::mangle_python(); }); m.def( diff --git a/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp b/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp index 4f6623235..234f63085 100644 --- a/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp +++ b/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp @@ -40,13 +40,12 @@ register_domain_descriptor(nanobind::module_& m) auto _domain_descriptor = register_class(m); - _domain_descriptor - .def(nanobind::init( - [](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()}; - })) + auto make_domain_descriptor = [](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()}; }; + + _domain_descriptor.def(nanobind::new_(make_domain_descriptor)) .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 5ab376a92..ad7f59a39 100644 --- a/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp @@ -28,6 +28,35 @@ namespace pyghex namespace unstructured { +namespace +{ +template +struct ndarray_device_type; + +template<> +struct ndarray_device_type +{ + using type = nanobind::device::cpu; +}; + +template<> +struct ndarray_device_type +{ +#ifdef __HIP_PLATFORM_HCC__ + using type = nanobind::device::rocm; +#else + using type = nanobind::device::cuda; +#endif +}; + +template +nanobind::ssize_t +byte_stride(const T& b, const size_t dim) +{ + return b.stride(dim) * static_cast(b.itemsize()); +} +} // namespace + void register_field_descriptor(nanobind::module_& m) { @@ -50,95 +79,88 @@ register_field_descriptor(nanobind::module_& m) auto _field_descriptor = register_class(m); register_class(m); - _field_descriptor.def( - nanobind::init( - [](const domain_descriptor_type& dom, - nanobind::ndarray b) + 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 = byte_stride(b, 0); + const auto stride_1 = (b.ndim() == 2) ? byte_stride(b, 1) : nanobind::ssize_t{0}; + if (b.ndim() == 2 && stride_1 != sizeof(T)) + { + levels_first = false; + if (stride_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)(stride_0) << "."; + throw nanobind::type_error(error.str().c_str()); + } + if (((std::size_t)(stride_1) % sizeof(T)) != 0) { - 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()); - } - - // 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 (b.ndim() == 2 && b.stride(1) != sizeof(T)) - { - levels_first = false; - if (b.stride(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)(b.stride(0)) - << "."; - throw nanobind::type_error(error.str().c_str()); - } - if (((std::size_t)(b.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)(b.stride(1)) - << " is a multiple of the element size " << sizeof(T) << "."; - throw nanobind::type_error(error.str().c_str()); - } - outer_strides = b.stride(1) / sizeof(T); - } - else if (b.ndim() == 2) - { - if (b.stride(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)(b.stride(1)) - << "."; - throw nanobind::type_error(error.str().c_str()); - } - if (((std::size_t)(b.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)(b.stride(0)) - << " is a multiple of the element size of " << sizeof(T) - << "."; - throw nanobind::type_error(error.str().c_str()); - } - outer_strides = b.stride(0) / sizeof(T); - } - else - { - // Note this case only happens for `info.ndim == 1`. - if (b.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 " << b.stride(0) << "."; - throw nanobind::type_error(error.str().c_str()); - }; - } - 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}; - }), + 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) + { + if (stride_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)(stride_1) << "."; + throw nanobind::type_error(error.str().c_str()); + } + 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}; + }; + + _field_descriptor.def(nanobind::new_(make_field_descriptor), nanobind::keep_alive<0, 2>()); }); } diff --git a/bindings/python/src/_pyghex/unstructured/halo_generator.cpp b/bindings/python/src/_pyghex/unstructured/halo_generator.cpp index 14b7b2152..a9887d8dd 100644 --- a/bindings/python/src/_pyghex/unstructured/halo_generator.cpp +++ b/bindings/python/src/_pyghex/unstructured/halo_generator.cpp @@ -36,10 +36,12 @@ register_halo_generator(nanobind::module_& m) auto _halo_generator = register_class(m); /*auto _halo = */ register_class(m); + auto make_halo_generator = []() { return type{}; }; + auto make_halo_generator_from_gids = [](const std::vector& gids) + { return type{gids}; }; - _halo_generator.def(nanobind::init<>(), "Create a halo generator") - .def(nanobind::init( - [](const std::vector& gids) { return type{gids}; })) + _halo_generator.def(nanobind::new_(make_halo_generator), "Create a halo generator") + .def(nanobind::new_(make_halo_generator_from_gids)) .def("__call__", &type::operator()); }); } diff --git a/bindings/python/src/_pyghex/unstructured/pattern.cpp b/bindings/python/src/_pyghex/unstructured/pattern.cpp index 41350ea77..15a0acc75 100644 --- a/bindings/python/src/_pyghex/unstructured/pattern.cpp +++ b/bindings/python/src/_pyghex/unstructured/pattern.cpp @@ -21,6 +21,7 @@ #include #include +#include namespace pyghex { @@ -49,9 +50,9 @@ register_pattern(nanobind::module_& m) auto _pattern_container = register_class(m); _pattern_container - .def_property_readonly_static("grid_type", [](const nanobind::object&) + .def_prop_ro_static("grid_type", [](const nanobind::object&) { return util::mangle_python(); }) - .def_property_readonly_static("domain_id_type", [](const nanobind::object&) + .def_prop_ro_static("domain_id_type", [](const nanobind::object&) { return util::mangle_python(); }); m.def( diff --git a/bindings/python/src/ghex/CMakeLists.txt b/bindings/python/src/ghex/CMakeLists.txt index 599c5af4b..495619e11 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/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index c65e7695e..592581b0b 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -2,6 +2,7 @@ include(GNUInstallDirs) if (GHEX_BUILD_PYTHON_BINDINGS) + find_package (Python REQUIRED COMPONENTS Interpreter Development.Module) find_package (Python3 REQUIRED COMPONENTS Interpreter Development.Module) if(${Python3_FOUND}) @@ -9,7 +10,8 @@ if (GHEX_BUILD_PYTHON_BINDINGS) endif() include(ghex_find_python_module) - # nanobind is found and linked in bindings/python/src/_pyghex/CMakeLists.txt + 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 c8f0fc5f3..9bdfbdd86 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 a54494657..6082b7f14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,7 @@ [build-system] build-backend = 'scikit_build_core.build' requires = [ - "setuptools>=42", - "scikit-build>=0.13", + "scikit-build-core>=0.5", "cmake>=3.12", "ninja", "nanobind>=2.0.0" diff --git a/test/bindings/python/CMakeLists.txt b/test/bindings/python/CMakeLists.txt index 2069165a3..b40377286 100644 --- a/test/bindings/python/CMakeLists.txt +++ b/test/bindings/python/CMakeLists.txt @@ -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) @@ -90,3 +91,4 @@ ghex_reg_parallel_pytest(structured_pattern 4) #ghex_reg_pytest(unstructured_domain_descriptor) ghex_reg_parallel_pytest(unstructured_domain_descriptor 4) +ghex_reg_parallel_pytest(unstructured_default_halo_generator 4) From a6ab6b43016b6416efa78e7f6f5f6cb00c2ac13a Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Wed, 18 Mar 2026 14:17:06 +0100 Subject: [PATCH 04/38] Only look for Python in cmake, not both Python and Python3 --- cmake/ghex_python.cmake | 7 ++----- test/bindings/python/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index 592581b0b..41fe9fed9 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -2,12 +2,9 @@ include(GNUInstallDirs) if (GHEX_BUILD_PYTHON_BINDINGS) - find_package (Python REQUIRED COMPONENTS Interpreter Development.Module) - 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_python_module(nanobind REQUIRED) diff --git a/test/bindings/python/CMakeLists.txt b/test/bindings/python/CMakeLists.txt index b40377286..4aa390ca4 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" ) From f8838301baf9b34c49361d47c133783e27b1e7d7 Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Wed, 18 Mar 2026 14:21:07 +0100 Subject: [PATCH 05/38] Remove Python.h include --- bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 7bade4c6c..365baa046 100644 --- a/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp +++ b/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp @@ -13,8 +13,6 @@ #include #include -#include - #include #include @@ -65,7 +63,7 @@ py_dtype_to_cpp_name(nb::handle dtype) using type = decltype(l); auto candidate_dtype = dtype_of(); - if (PyObject_RichCompareBool(canonical_dtype.ptr(), candidate_dtype.ptr(), Py_EQ) == 1) + if (nb::bool_(canonical_dtype.equal(candidate_dtype))) { assert(cpp_name.empty()); cpp_name = util::mangle_python(); From 1a9fc3b7b6ff719989585c935d79913a8b86d97b Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Wed, 18 Mar 2026 15:37:40 +0100 Subject: [PATCH 06/38] Remove pybind-version from config --- bindings/python/src/_pyghex/config.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/bindings/python/src/_pyghex/config.cpp b/bindings/python/src/_pyghex/config.cpp index ceab6d83d..49ec4f8fe 100644 --- a/bindings/python/src/_pyghex/config.cpp +++ b/bindings/python/src/_pyghex/config.cpp @@ -52,7 +52,6 @@ config() } { const char* version = mk_ver(NB_VERSION_MAJOR, NB_VERSION_MINOR, NB_VERSION_PATCH); - dict[nanobind::str("pybind-version")] = nanobind::str(version); dict[nanobind::str("nanobind-version")] = nanobind::str(version); } #undef mk_str From d08d73c2bf10632a7ce997c2748c006a3ed4219a Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Wed, 18 Mar 2026 17:19:50 +0100 Subject: [PATCH 07/38] Minor cleanup --- bindings/python/src/_pyghex/module.cpp | 32 +++++++++---------- .../src/_pyghex/py_dtype_to_cpp_name.cpp | 26 +++++++-------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/bindings/python/src/_pyghex/module.cpp b/bindings/python/src/_pyghex/module.cpp index 06fe9139f..e9bf5d8d3 100644 --- a/bindings/python/src/_pyghex/module.cpp +++ b/bindings/python/src/_pyghex/module.cpp @@ -9,41 +9,39 @@ */ #include -namespace nb = nanobind; - namespace pyghex { -void register_config(nb::module_& m); -void register_mpi(nb::module_& m); -void register_context(nb::module_& m); -void register_py_dtype_to_cpp_name(nb::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(nb::module_& m); -void register_halo_generator(nb::module_& m); -void register_field_descriptor(nb::module_& m); -void register_pattern(nb::module_& m); -void register_communication_object(nb::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(nb::module_& m); -void register_halo_generator(nb::module_& m); -void register_field_descriptor(nb::module_& m); -void register_pattern(nb::module_& m); -void register_communication_object(nb::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 NB_MODULE(_pyghex, m) { - // m.doc() = "nanobind 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/py_dtype_to_cpp_name.cpp b/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp index 365baa046..96838a5af 100644 --- a/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp +++ b/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp @@ -19,40 +19,38 @@ #include #include -namespace nb = nanobind; - namespace pyghex { namespace { -nb::object -numpy_dtype(const nb::handle& value) +nanobind::object +numpy_dtype(const nanobind::handle& value) { - static nb::module_ numpy = nb::module_::import_("numpy"); - return numpy.attr("dtype")(nb::borrow(value)); + return nanobind::module_::import_("numpy").attr("dtype")( + nanobind::borrow(value)); } template -nb::object +nanobind::object dtype_of() { - if constexpr (std::is_same_v) return numpy_dtype(nb::str("float64")); + if constexpr (std::is_same_v) return numpy_dtype(nanobind::str("float64")); else if constexpr (std::is_same_v) - return numpy_dtype(nb::str("float32")); + return numpy_dtype(nanobind::str("float32")); else if constexpr (std::is_same_v) - return numpy_dtype(nb::str("bool")); + return numpy_dtype(nanobind::str("bool")); else if constexpr (std::is_same_v) - return numpy_dtype(nb::str("int32")); + return numpy_dtype(nanobind::str("int32")); else if constexpr (std::is_same_v) - return numpy_dtype(nb::str("int64")); + return numpy_dtype(nanobind::str("int64")); else static_assert(sizeof(T) == 0, "unsupported dtype"); } } // namespace std::string -py_dtype_to_cpp_name(nb::handle dtype) +py_dtype_to_cpp_name(nanobind::handle dtype) { const auto canonical_dtype = numpy_dtype(dtype); std::string cpp_name; @@ -63,7 +61,7 @@ py_dtype_to_cpp_name(nb::handle dtype) using type = decltype(l); auto candidate_dtype = dtype_of(); - if (nb::bool_(canonical_dtype.equal(candidate_dtype))) + if (nanobind::bool_(canonical_dtype.equal(candidate_dtype))) { assert(cpp_name.empty()); cpp_name = util::mangle_python(); From 3b463ba6870aa704b22859bd0b59718b473cbfa4 Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Wed, 18 Mar 2026 17:26:42 +0100 Subject: [PATCH 08/38] Remove unused include --- bindings/python/src/_pyghex/context_shim.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/bindings/python/src/_pyghex/context_shim.hpp b/bindings/python/src/_pyghex/context_shim.hpp index d23f94728..f89b9405c 100644 --- a/bindings/python/src/_pyghex/context_shim.hpp +++ b/bindings/python/src/_pyghex/context_shim.hpp @@ -9,8 +9,6 @@ */ #pragma once -#include - #include #include From 5303826ce4994bcc4d9c7571874b1c17bc982313 Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Wed, 18 Mar 2026 17:36:19 +0100 Subject: [PATCH 09/38] Bugfixes --- .../_pyghex/structured/regular/field_descriptor.cpp | 2 +- .../src/_pyghex/unstructured/field_descriptor.cpp | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp index 2fc45640e..94e327620 100644 --- a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp @@ -132,7 +132,7 @@ register_field_descriptor(nanobind::module_& m) }; _field_descriptor.def(nanobind::new_(make_field_descriptor), - nanobind::keep_alive<0, 2>()); + nanobind::keep_alive<0, 3>()); }); } diff --git a/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp b/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp index ad7f59a39..ddbb2327d 100644 --- a/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp @@ -127,14 +127,7 @@ register_field_descriptor(nanobind::module_& m) } else if (b.ndim() == 2) { - if (stride_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)(stride_1) << "."; - throw nanobind::type_error(error.str().c_str()); - } + // stride_1 == sizeof(T): levels are the inner (fast) dimension if (((std::size_t)(stride_0) % sizeof(T)) != 0) { std::stringstream error; @@ -161,7 +154,7 @@ register_field_descriptor(nanobind::module_& m) }; _field_descriptor.def(nanobind::new_(make_field_descriptor), - nanobind::keep_alive<0, 2>()); + nanobind::keep_alive<0, 3>()); }); } From 2194626dda23f6191e999a4f3a2ab5daad59e90b Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Wed, 18 Mar 2026 22:50:51 +0100 Subject: [PATCH 10/38] Add nanobind-dev to ci --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 18a6b401e..6d71529cd 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 ninja-build + apt-get -y install python3-dev python3-venv nanobind-dev ninja-build - name: Clone w/ submodules uses: actions/checkout@v3 From 54670635b23a2992671973027fb58e4bdcd1f1b5 Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Thu, 19 Mar 2026 16:12:24 +0100 Subject: [PATCH 11/38] Remove test --- test/bindings/python/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test/bindings/python/CMakeLists.txt b/test/bindings/python/CMakeLists.txt index 4aa390ca4..c48ea5c97 100644 --- a/test/bindings/python/CMakeLists.txt +++ b/test/bindings/python/CMakeLists.txt @@ -91,4 +91,3 @@ ghex_reg_parallel_pytest(structured_pattern 4) #ghex_reg_pytest(unstructured_domain_descriptor) ghex_reg_parallel_pytest(unstructured_domain_descriptor 4) -ghex_reg_parallel_pytest(unstructured_default_halo_generator 4) From 8f08f9d1ac4b38c7d2b9b2fb868cc829d0a29d2c Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Thu, 19 Mar 2026 16:17:17 +0100 Subject: [PATCH 12/38] Require consistent cmake versions --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6082b7f14..886c912cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ build-backend = 'scikit_build_core.build' requires = [ "scikit-build-core>=0.5", - "cmake>=3.12", + "cmake>=3.21", "ninja", "nanobind>=2.0.0" ] From 2ee51748df440dfacbb0249b1843fb0a68a37b68 Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Thu, 19 Mar 2026 16:23:24 +0100 Subject: [PATCH 13/38] Clean up pyproject build-system requires --- pyproject.toml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 886c912cf..d544fb101 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,9 @@ [build-system] build-backend = 'scikit_build_core.build' requires = [ - "scikit-build-core>=0.5", - "cmake>=3.21", - "ninja", - "nanobind>=2.0.0" + 'cmake', + 'nanobind>=2.0.0' + 'scikit-build-core', ] [project] From dfeb57668db105514b0884f9f375a11a9ec4c78e Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Thu, 19 Mar 2026 16:32:47 +0100 Subject: [PATCH 14/38] Revert some overeager changes --- .../src/_pyghex/unstructured/domain_descriptor.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp b/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp index 234f63085..87d58530c 100644 --- a/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp +++ b/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp @@ -40,12 +40,13 @@ register_domain_descriptor(nanobind::module_& m) auto _domain_descriptor = register_class(m); - auto make_domain_descriptor = [](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()}; }; - - _domain_descriptor.def(nanobind::new_(make_domain_descriptor)) + _domain_descriptor + .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()}; + })) .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") From 439c70f12ad4747f31b090fae6cac74bc29a0427 Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Thu, 19 Mar 2026 17:29:46 +0100 Subject: [PATCH 15/38] Fix typo in pyproject.toml --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d544fb101..50971c2c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,8 +2,8 @@ build-backend = 'scikit_build_core.build' requires = [ 'cmake', - 'nanobind>=2.0.0' - 'scikit-build-core', + 'nanobind>=2.0.0', + 'scikit-build-core' ] [project] From 74f1fe9d98a26ad894d745ebbc1e32136b2e012f Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Thu, 19 Mar 2026 19:03:13 +0100 Subject: [PATCH 16/38] Support nanobind 1.x --- .../structured/regular/field_descriptor.cpp | 21 +++++++++++++------ .../unstructured/domain_descriptor.cpp | 9 ++++++++ .../_pyghex/unstructured/field_descriptor.cpp | 20 +++++++++++------- .../_pyghex/unstructured/halo_generator.cpp | 7 ++----- pyproject.toml | 2 +- 5 files changed, 39 insertions(+), 20 deletions(-) diff --git a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp index 94e327620..953eb872e 100644 --- a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp @@ -60,11 +60,10 @@ struct ndarray_device_type }; template -std::vector -byte_strides(const T& b) +std::vector +byte_strides(const T& b, std::ptrdiff_t itemsize) { - std::vector result(b.ndim()); - const auto itemsize = static_cast(b.itemsize()); + std::vector result(b.ndim()); for (size_t i = 0; i < b.ndim(); ++i) result[i] = b.stride(i) * itemsize; return result; } @@ -110,11 +109,11 @@ register_field_descriptor(nanobind::module_& m) throw nanobind::type_error(error.str().c_str()); } - auto strides = byte_strides(b); + auto strides = byte_strides(b, sizeof(T)); auto ordered_strides = strides; std::sort(ordered_strides.begin(), ordered_strides.end(), - [](nanobind::ssize_t a, nanobind::ssize_t b) { return a > b; }); + [](std::ptrdiff_t a, std::ptrdiff_t b) { return a > b; }); array b_layout_map; for (size_t i = 0; i < dimension::value; ++i) @@ -131,8 +130,18 @@ register_field_descriptor(nanobind::module_& m) 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/unstructured/domain_descriptor.cpp b/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp index 87d58530c..d353dda40 100644 --- a/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp +++ b/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp @@ -41,12 +41,21 @@ register_domain_descriptor(nanobind::module_& m) auto _domain_descriptor = register_class(m); _domain_descriptor +#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 ddbb2327d..790853dbd 100644 --- a/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp +++ b/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp @@ -49,12 +49,6 @@ struct ndarray_device_type #endif }; -template -nanobind::ssize_t -byte_stride(const T& b, const size_t dim) -{ - return b.stride(dim) * static_cast(b.itemsize()); -} } // namespace void @@ -101,8 +95,9 @@ register_field_descriptor(nanobind::module_& m) bool levels_first = true; std::size_t outer_strides = 0u; - const auto stride_0 = byte_stride(b, 0); - const auto stride_1 = (b.ndim() == 2) ? byte_stride(b, 1) : nanobind::ssize_t{0}; + 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; @@ -153,8 +148,17 @@ register_field_descriptor(nanobind::module_& m) 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 a9887d8dd..dc5432898 100644 --- a/bindings/python/src/_pyghex/unstructured/halo_generator.cpp +++ b/bindings/python/src/_pyghex/unstructured/halo_generator.cpp @@ -36,12 +36,9 @@ register_halo_generator(nanobind::module_& m) auto _halo_generator = register_class(m); /*auto _halo = */ register_class(m); - auto make_halo_generator = []() { return type{}; }; - auto make_halo_generator_from_gids = [](const std::vector& gids) - { return type{gids}; }; - _halo_generator.def(nanobind::new_(make_halo_generator), "Create a halo generator") - .def(nanobind::new_(make_halo_generator_from_gids)) + _halo_generator.def(nanobind::init<>(), "Create a halo generator") + .def(nanobind::init>()) .def("__call__", &type::operator()); }); } diff --git a/pyproject.toml b/pyproject.toml index 50971c2c0..5070365e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ build-backend = 'scikit_build_core.build' requires = [ 'cmake', - 'nanobind>=2.0.0', + 'nanobind>=1.0.0', 'scikit-build-core' ] From 1d8945aa0bc520b167bd61c8dca3a886a63442a2 Mon Sep 17 00:00:00 2001 From: Mikael Simberg Date: Thu, 19 Mar 2026 19:12:15 +0100 Subject: [PATCH 17/38] Try to fix cuda build with nanobind --- .../src/_pyghex/unstructured/communication_object.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bindings/python/src/_pyghex/unstructured/communication_object.cpp b/bindings/python/src/_pyghex/unstructured/communication_object.cpp index 73f213d95..4c21764e1 100644 --- a/bindings/python/src/_pyghex/unstructured/communication_object.cpp +++ b/bindings/python/src/_pyghex/unstructured/communication_object.cpp @@ -49,7 +49,8 @@ extract_cuda_stream(nanobind::object python_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 - nanobind::tuple cuda_stream_protocol = python_stream.attr("__cuda_stream__")(); + nanobind::tuple cuda_stream_protocol = + nanobind::cast(python_stream.attr("__cuda_stream__")()); if (cuda_stream_protocol.size() != 2) { std::stringstream error; @@ -58,7 +59,7 @@ extract_cuda_stream(nanobind::object python_stream) throw nanobind::type_error(error.str().c_str()); } - 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; @@ -67,13 +68,14 @@ extract_cuda_stream(nanobind::object python_stream) 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 (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__`. From 677862ae2dd23ea6193ae97d9c2a8489e3b58792 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Wed, 8 Apr 2026 14:49:11 +0200 Subject: [PATCH 18/38] If `nanobind` is installed on the system it might not be found. While the Python package is picked up correctly, the `nanobind-config.cmake` is not. Instead of the one that ships with the Python package the one of the system is found, which is in a cmake directory. However, then the headers are not found. The solution is to move the found path up in the search hirarchy. --- cmake/ghex_python.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index 41fe9fed9..e3abc2fc1 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -8,7 +8,7 @@ if (GHEX_BUILD_PYTHON_BINDINGS) include(ghex_find_python_module) find_python_module(nanobind REQUIRED) - find_package(nanobind CONFIG REQUIRED PATHS "${PY_NANOBIND}/cmake") + find_package(nanobind CONFIG REQUIRED HINTS "${PY_NANOBIND}/cmake") # Ask Python where it keeps its system (platform) packages. file(WRITE "${CMAKE_BINARY_DIR}/install-prefix" "${CMAKE_INSTALL_PREFIX}") From be58b34596c2def27bab578fb11985f16a3ab9e1 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 09:15:10 +0200 Subject: [PATCH 19/38] First version that does nto fully work yet. --- bindings/python/src/_pyghex/CMakeLists.txt | 22 ++++++++++++++++++++- bindings/python/src/ghex/pyghex/__init__.py | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/bindings/python/src/_pyghex/CMakeLists.txt b/bindings/python/src/_pyghex/CMakeLists.txt index 00c9030f1..1ac8075df 100644 --- a/bindings/python/src/_pyghex/CMakeLists.txt +++ b/bindings/python/src/_pyghex/CMakeLists.txt @@ -42,18 +42,38 @@ 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. 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 # Choose this particular output directory for testing purposes - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../../ghex") + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../../ghex/pyghex") # This dependency has to be spelt out again, despite being added to # pyghex_obj because CMake. target_link_libraries(pyghex PRIVATE ghex) ghex_link_to_oomph(ghex) +nanobind_add_stub( + pyghex_stub + MODULE _pyghex + OUTPUT __init__.pyi # If `Recursive` is not given then it is generated otherwise it is ignored. + #RECURSIVE + PYTHON_PATH $ + #OUTPUT_PATH "${CMAKE_CURRENT_BINARY_DIR}/../../ghex/pyghex" + #PYTHON_PATH "." + DEPENDS ghex +) + + + + + + + + + # Set RPaths such that the python module is able to find libghex include(ghex_rpath) if (SKBUILD_PROJECT_NAME) diff --git a/bindings/python/src/ghex/pyghex/__init__.py b/bindings/python/src/ghex/pyghex/__init__.py index 8d2f5ff4d..f3e2f2182 100644 --- a/bindings/python/src/ghex/pyghex/__init__.py +++ b/bindings/python/src/ghex/pyghex/__init__.py @@ -14,4 +14,4 @@ # The library will be installed in the same path as this file, which will # import the compiled part of the wrapper from the _pyghex namespace. -from .._pyghex import * # noqa:F403 +from ._pyghex import * # noqa:F403 From e5bcca9ee085b81b2d72a8e12d09a20e86dd6abf Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 09:52:05 +0200 Subject: [PATCH 20/38] Fixed an import issue. But I do not see how `_pyghex` was ever imported? Probably it is injected somehow. --- bindings/python/src/ghex/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/src/ghex/__init__.py b/bindings/python/src/ghex/__init__.py index 318faebe4..a5f32a69a 100644 --- a/bindings/python/src/ghex/__init__.py +++ b/bindings/python/src/ghex/__init__.py @@ -29,7 +29,7 @@ def get_version() -> str: __version__ = get_version() -__config__ = _pyghex.config() # noqa:F405 +__config__ = pyghex.config() # noqa:F405 # Remove get_version from module. del get_version From 00088e6170f8fedc7b47305bdac3fbe22b4e0205 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 10:56:45 +0200 Subject: [PATCH 21/38] Now the names are a bit different and the `pyghex` vs. `_pyghex` no longer exist. --- bindings/python/src/_pyghex/CMakeLists.txt | 7 ++++--- bindings/python/src/_pyghex/module.cpp | 2 +- bindings/python/src/ghex/pyghex/__init__.py | 17 ----------------- bindings/python/src/ghex/util.py | 6 +++--- 4 files changed, 8 insertions(+), 24 deletions(-) delete mode 100644 bindings/python/src/ghex/pyghex/__init__.py diff --git a/bindings/python/src/_pyghex/CMakeLists.txt b/bindings/python/src/_pyghex/CMakeLists.txt index 1ac8075df..4b9b799f8 100644 --- a/bindings/python/src/_pyghex/CMakeLists.txt +++ b/bindings/python/src/_pyghex/CMakeLists.txt @@ -47,17 +47,18 @@ 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 + #OUTPUT_NAME _pyghex # Choose this particular output directory for testing purposes - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../../ghex/pyghex") + 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 ghex) ghex_link_to_oomph(ghex) +# If compiled with `make -j 6` this fails, because it is not fully build, there is a dependency that is not set correctly. nanobind_add_stub( pyghex_stub - MODULE _pyghex + MODULE pyghex OUTPUT __init__.pyi # If `Recursive` is not given then it is generated otherwise it is ignored. #RECURSIVE PYTHON_PATH $ diff --git a/bindings/python/src/_pyghex/module.cpp b/bindings/python/src/_pyghex/module.cpp index e9bf5d8d3..4ed46dde4 100644 --- a/bindings/python/src/_pyghex/module.cpp +++ b/bindings/python/src/_pyghex/module.cpp @@ -39,7 +39,7 @@ void register_communication_object(nanobind::module_& m); } // namespace pyghex -NB_MODULE(_pyghex, m) +NB_MODULE(pyghex, m) { m.doc() = "nanobind ghex bindings"; diff --git a/bindings/python/src/ghex/pyghex/__init__.py b/bindings/python/src/ghex/pyghex/__init__.py deleted file mode 100644 index f3e2f2182..000000000 --- a/bindings/python/src/ghex/pyghex/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -# ghex-org -# -# Copyright (c) 2014-2023, ETH Zurich -# All rights reserved. -# -# Please, refer to the LICENSE file in the root directory. -# SPDX-License-Identifier: BSD-3-Clause -# - -# 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 -# import the compiled part of the wrapper from the _pyghex namespace. - -from ._pyghex import * # noqa:F403 diff --git a/bindings/python/src/ghex/util.py b/bindings/python/src/ghex/util.py index 1d92b1e79..3436bf999 100644 --- a/bindings/python/src/ghex/util.py +++ b/bindings/python/src/ghex/util.py @@ -12,7 +12,7 @@ import inspect from typing import TYPE_CHECKING -import ghex.pyghex as _pyghex +from ghex import pyghex if TYPE_CHECKING: from numpy.typing import DTypeLike @@ -30,7 +30,7 @@ def unwrap(arg: Any) -> Any: def cls_from_cpp_type_spec(cpp_type_spec: Union[str, tuple[str, ...]]) -> Any: if isinstance(cpp_type_spec, str): - return getattr(_pyghex, cpp_type_spec) + return getattr(pyghex, cpp_type_spec) else: fq_cpp_type_name, *template_args = cpp_type_spec template_args = [ @@ -39,7 +39,7 @@ def cls_from_cpp_type_spec(cpp_type_spec: Union[str, tuple[str, ...]]) -> Any: ] fq_cpp_type_specialization_name = fq_cpp_type_name + "_" + "_".join(template_args) + "_" - return getattr(_pyghex, fq_cpp_type_specialization_name) + return getattr(pyghex, fq_cpp_type_specialization_name) class CppWrapper: From 2fc822d5ac92994b9f2acf83f0811ee6f97a67d4 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 11:20:45 +0200 Subject: [PATCH 22/38] Now the file is transported as it should. --- bindings/python/src/_pyghex/CMakeLists.txt | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/bindings/python/src/_pyghex/CMakeLists.txt b/bindings/python/src/_pyghex/CMakeLists.txt index 4b9b799f8..b6ad6178c 100644 --- a/bindings/python/src/_pyghex/CMakeLists.txt +++ b/bindings/python/src/_pyghex/CMakeLists.txt @@ -44,11 +44,8 @@ compile_as_cuda(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} SOURCES ${pyghex_source}) 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 +# Choose this particular output directory for testing purposes set_target_properties(pyghex PROPERTIES - #OUTPUT_NAME _pyghex - # 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. @@ -59,22 +56,13 @@ ghex_link_to_oomph(ghex) nanobind_add_stub( pyghex_stub MODULE pyghex - OUTPUT __init__.pyi # If `Recursive` is not given then it is generated otherwise it is ignored. - #RECURSIVE + OUTPUT pyghex.pyi + RECURSIVE # We need to specify `RECURSIVE` to enable `OUTPUT_PATH`. PYTHON_PATH $ - #OUTPUT_PATH "${CMAKE_CURRENT_BINARY_DIR}/../../ghex/pyghex" - #PYTHON_PATH "." + OUTPUT_PATH "${CMAKE_CURRENT_BINARY_DIR}/../../ghex" DEPENDS ghex ) - - - - - - - - # Set RPaths such that the python module is able to find libghex include(ghex_rpath) if (SKBUILD_PROJECT_NAME) From 075590fb934d6c6fe1a0f1775cfd834c4d6ded97 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 11:21:35 +0200 Subject: [PATCH 23/38] Relocated this file. --- bindings/python/CMakeLists.txt | 2 +- bindings/python/src/{_pyghex => pyghex}/CMakeLists.txt | 0 bindings/python/src/{_pyghex => pyghex}/config.cpp | 0 bindings/python/src/{_pyghex => pyghex}/context_shim.cpp | 0 bindings/python/src/{_pyghex => pyghex}/context_shim.hpp | 0 bindings/python/src/{_pyghex => pyghex}/module.cpp | 0 bindings/python/src/{_pyghex => pyghex}/mpi_comm_shim.cpp | 0 bindings/python/src/{_pyghex => pyghex}/mpi_comm_shim.hpp | 0 .../python/src/{_pyghex => pyghex}/py_dtype_to_cpp_name.cpp | 0 bindings/python/src/{_pyghex => pyghex}/register_class.hpp | 0 .../structured/regular/communication_object.cpp | 0 .../structured/regular/communication_object.hpp | 0 .../structured/regular/domain_descriptor.cpp | 0 .../structured/regular/domain_descriptor.hpp | 0 .../{_pyghex => pyghex}/structured/regular/field_descriptor.cpp | 0 .../{_pyghex => pyghex}/structured/regular/field_descriptor.hpp | 0 .../{_pyghex => pyghex}/structured/regular/halo_generator.cpp | 0 .../{_pyghex => pyghex}/structured/regular/halo_generator.hpp | 0 .../src/{_pyghex => pyghex}/structured/regular/pattern.cpp | 0 .../src/{_pyghex => pyghex}/structured/regular/pattern.hpp | 0 bindings/python/src/{_pyghex => pyghex}/structured/types.hpp | 0 bindings/python/src/{_pyghex => pyghex}/types.hpp | 0 .../{_pyghex => pyghex}/unstructured/communication_object.cpp | 0 .../{_pyghex => pyghex}/unstructured/communication_object.hpp | 0 .../src/{_pyghex => pyghex}/unstructured/domain_descriptor.cpp | 0 .../src/{_pyghex => pyghex}/unstructured/domain_descriptor.hpp | 0 .../src/{_pyghex => pyghex}/unstructured/field_descriptor.cpp | 0 .../src/{_pyghex => pyghex}/unstructured/field_descriptor.hpp | 0 .../src/{_pyghex => pyghex}/unstructured/halo_generator.cpp | 0 .../src/{_pyghex => pyghex}/unstructured/halo_generator.hpp | 0 .../python/src/{_pyghex => pyghex}/unstructured/pattern.cpp | 0 .../python/src/{_pyghex => pyghex}/unstructured/pattern.hpp | 0 bindings/python/src/{_pyghex => pyghex}/unstructured/types.hpp | 0 bindings/python/src/{_pyghex => pyghex}/util/demangle.hpp | 0 bindings/python/src/{_pyghex => pyghex}/util/to_string.hpp | 0 35 files changed, 1 insertion(+), 1 deletion(-) rename bindings/python/src/{_pyghex => pyghex}/CMakeLists.txt (100%) rename bindings/python/src/{_pyghex => pyghex}/config.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/context_shim.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/context_shim.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/module.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/mpi_comm_shim.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/mpi_comm_shim.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/py_dtype_to_cpp_name.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/register_class.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/communication_object.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/communication_object.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/domain_descriptor.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/domain_descriptor.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/field_descriptor.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/field_descriptor.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/halo_generator.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/halo_generator.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/pattern.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/regular/pattern.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/structured/types.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/types.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/communication_object.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/communication_object.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/domain_descriptor.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/domain_descriptor.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/field_descriptor.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/field_descriptor.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/halo_generator.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/halo_generator.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/pattern.cpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/pattern.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/unstructured/types.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/util/demangle.hpp (100%) rename bindings/python/src/{_pyghex => pyghex}/util/to_string.hpp (100%) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 22def264f..7bea09d79 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -1,2 +1,2 @@ -add_subdirectory(src/_pyghex) +add_subdirectory(src/pyghex) add_subdirectory(src/ghex) diff --git a/bindings/python/src/_pyghex/CMakeLists.txt b/bindings/python/src/pyghex/CMakeLists.txt similarity index 100% rename from bindings/python/src/_pyghex/CMakeLists.txt rename to bindings/python/src/pyghex/CMakeLists.txt diff --git a/bindings/python/src/_pyghex/config.cpp b/bindings/python/src/pyghex/config.cpp similarity index 100% rename from bindings/python/src/_pyghex/config.cpp rename to bindings/python/src/pyghex/config.cpp diff --git a/bindings/python/src/_pyghex/context_shim.cpp b/bindings/python/src/pyghex/context_shim.cpp similarity index 100% rename from bindings/python/src/_pyghex/context_shim.cpp rename to bindings/python/src/pyghex/context_shim.cpp diff --git a/bindings/python/src/_pyghex/context_shim.hpp b/bindings/python/src/pyghex/context_shim.hpp similarity index 100% rename from bindings/python/src/_pyghex/context_shim.hpp rename to bindings/python/src/pyghex/context_shim.hpp diff --git a/bindings/python/src/_pyghex/module.cpp b/bindings/python/src/pyghex/module.cpp similarity index 100% rename from bindings/python/src/_pyghex/module.cpp rename to bindings/python/src/pyghex/module.cpp diff --git a/bindings/python/src/_pyghex/mpi_comm_shim.cpp b/bindings/python/src/pyghex/mpi_comm_shim.cpp similarity index 100% rename from bindings/python/src/_pyghex/mpi_comm_shim.cpp rename to bindings/python/src/pyghex/mpi_comm_shim.cpp diff --git a/bindings/python/src/_pyghex/mpi_comm_shim.hpp b/bindings/python/src/pyghex/mpi_comm_shim.hpp similarity index 100% rename from bindings/python/src/_pyghex/mpi_comm_shim.hpp rename to bindings/python/src/pyghex/mpi_comm_shim.hpp diff --git a/bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp b/bindings/python/src/pyghex/py_dtype_to_cpp_name.cpp similarity index 100% rename from bindings/python/src/_pyghex/py_dtype_to_cpp_name.cpp rename to bindings/python/src/pyghex/py_dtype_to_cpp_name.cpp diff --git a/bindings/python/src/_pyghex/register_class.hpp b/bindings/python/src/pyghex/register_class.hpp similarity index 100% rename from bindings/python/src/_pyghex/register_class.hpp rename to bindings/python/src/pyghex/register_class.hpp diff --git a/bindings/python/src/_pyghex/structured/regular/communication_object.cpp b/bindings/python/src/pyghex/structured/regular/communication_object.cpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/communication_object.cpp rename to bindings/python/src/pyghex/structured/regular/communication_object.cpp diff --git a/bindings/python/src/_pyghex/structured/regular/communication_object.hpp b/bindings/python/src/pyghex/structured/regular/communication_object.hpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/communication_object.hpp rename to bindings/python/src/pyghex/structured/regular/communication_object.hpp diff --git a/bindings/python/src/_pyghex/structured/regular/domain_descriptor.cpp b/bindings/python/src/pyghex/structured/regular/domain_descriptor.cpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/domain_descriptor.cpp rename to bindings/python/src/pyghex/structured/regular/domain_descriptor.cpp diff --git a/bindings/python/src/_pyghex/structured/regular/domain_descriptor.hpp b/bindings/python/src/pyghex/structured/regular/domain_descriptor.hpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/domain_descriptor.hpp rename to bindings/python/src/pyghex/structured/regular/domain_descriptor.hpp diff --git a/bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp b/bindings/python/src/pyghex/structured/regular/field_descriptor.cpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/field_descriptor.cpp rename to bindings/python/src/pyghex/structured/regular/field_descriptor.cpp diff --git a/bindings/python/src/_pyghex/structured/regular/field_descriptor.hpp b/bindings/python/src/pyghex/structured/regular/field_descriptor.hpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/field_descriptor.hpp rename to bindings/python/src/pyghex/structured/regular/field_descriptor.hpp diff --git a/bindings/python/src/_pyghex/structured/regular/halo_generator.cpp b/bindings/python/src/pyghex/structured/regular/halo_generator.cpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/halo_generator.cpp rename to bindings/python/src/pyghex/structured/regular/halo_generator.cpp diff --git a/bindings/python/src/_pyghex/structured/regular/halo_generator.hpp b/bindings/python/src/pyghex/structured/regular/halo_generator.hpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/halo_generator.hpp rename to bindings/python/src/pyghex/structured/regular/halo_generator.hpp diff --git a/bindings/python/src/_pyghex/structured/regular/pattern.cpp b/bindings/python/src/pyghex/structured/regular/pattern.cpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/pattern.cpp rename to bindings/python/src/pyghex/structured/regular/pattern.cpp diff --git a/bindings/python/src/_pyghex/structured/regular/pattern.hpp b/bindings/python/src/pyghex/structured/regular/pattern.hpp similarity index 100% rename from bindings/python/src/_pyghex/structured/regular/pattern.hpp rename to bindings/python/src/pyghex/structured/regular/pattern.hpp diff --git a/bindings/python/src/_pyghex/structured/types.hpp b/bindings/python/src/pyghex/structured/types.hpp similarity index 100% rename from bindings/python/src/_pyghex/structured/types.hpp rename to bindings/python/src/pyghex/structured/types.hpp diff --git a/bindings/python/src/_pyghex/types.hpp b/bindings/python/src/pyghex/types.hpp similarity index 100% rename from bindings/python/src/_pyghex/types.hpp rename to bindings/python/src/pyghex/types.hpp diff --git a/bindings/python/src/_pyghex/unstructured/communication_object.cpp b/bindings/python/src/pyghex/unstructured/communication_object.cpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/communication_object.cpp rename to bindings/python/src/pyghex/unstructured/communication_object.cpp diff --git a/bindings/python/src/_pyghex/unstructured/communication_object.hpp b/bindings/python/src/pyghex/unstructured/communication_object.hpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/communication_object.hpp rename to bindings/python/src/pyghex/unstructured/communication_object.hpp diff --git a/bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp b/bindings/python/src/pyghex/unstructured/domain_descriptor.cpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/domain_descriptor.cpp rename to bindings/python/src/pyghex/unstructured/domain_descriptor.cpp diff --git a/bindings/python/src/_pyghex/unstructured/domain_descriptor.hpp b/bindings/python/src/pyghex/unstructured/domain_descriptor.hpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/domain_descriptor.hpp rename to bindings/python/src/pyghex/unstructured/domain_descriptor.hpp diff --git a/bindings/python/src/_pyghex/unstructured/field_descriptor.cpp b/bindings/python/src/pyghex/unstructured/field_descriptor.cpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/field_descriptor.cpp rename to bindings/python/src/pyghex/unstructured/field_descriptor.cpp diff --git a/bindings/python/src/_pyghex/unstructured/field_descriptor.hpp b/bindings/python/src/pyghex/unstructured/field_descriptor.hpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/field_descriptor.hpp rename to bindings/python/src/pyghex/unstructured/field_descriptor.hpp diff --git a/bindings/python/src/_pyghex/unstructured/halo_generator.cpp b/bindings/python/src/pyghex/unstructured/halo_generator.cpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/halo_generator.cpp rename to bindings/python/src/pyghex/unstructured/halo_generator.cpp diff --git a/bindings/python/src/_pyghex/unstructured/halo_generator.hpp b/bindings/python/src/pyghex/unstructured/halo_generator.hpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/halo_generator.hpp rename to bindings/python/src/pyghex/unstructured/halo_generator.hpp diff --git a/bindings/python/src/_pyghex/unstructured/pattern.cpp b/bindings/python/src/pyghex/unstructured/pattern.cpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/pattern.cpp rename to bindings/python/src/pyghex/unstructured/pattern.cpp diff --git a/bindings/python/src/_pyghex/unstructured/pattern.hpp b/bindings/python/src/pyghex/unstructured/pattern.hpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/pattern.hpp rename to bindings/python/src/pyghex/unstructured/pattern.hpp diff --git a/bindings/python/src/_pyghex/unstructured/types.hpp b/bindings/python/src/pyghex/unstructured/types.hpp similarity index 100% rename from bindings/python/src/_pyghex/unstructured/types.hpp rename to bindings/python/src/pyghex/unstructured/types.hpp diff --git a/bindings/python/src/_pyghex/util/demangle.hpp b/bindings/python/src/pyghex/util/demangle.hpp similarity index 100% rename from bindings/python/src/_pyghex/util/demangle.hpp rename to bindings/python/src/pyghex/util/demangle.hpp diff --git a/bindings/python/src/_pyghex/util/to_string.hpp b/bindings/python/src/pyghex/util/to_string.hpp similarity index 100% rename from bindings/python/src/_pyghex/util/to_string.hpp rename to bindings/python/src/pyghex/util/to_string.hpp From f9e5150bc611cd7ba74f27def23ecc184f1211eb Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 11:25:37 +0200 Subject: [PATCH 24/38] Some modification in the path. --- bindings/python/src/pyghex/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/src/pyghex/CMakeLists.txt b/bindings/python/src/pyghex/CMakeLists.txt index b6ad6178c..707c57d29 100644 --- a/bindings/python/src/pyghex/CMakeLists.txt +++ b/bindings/python/src/pyghex/CMakeLists.txt @@ -59,7 +59,7 @@ nanobind_add_stub( OUTPUT pyghex.pyi RECURSIVE # We need to specify `RECURSIVE` to enable `OUTPUT_PATH`. PYTHON_PATH $ - OUTPUT_PATH "${CMAKE_CURRENT_BINARY_DIR}/../../ghex" + OUTPUT_PATH $ DEPENDS ghex ) From c2c98f69935e0aa988deca421e38a9ee114be0ae Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 11:37:17 +0200 Subject: [PATCH 25/38] Now there is a proper dependency. --- bindings/python/src/pyghex/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/python/src/pyghex/CMakeLists.txt b/bindings/python/src/pyghex/CMakeLists.txt index 707c57d29..937b5b3e2 100644 --- a/bindings/python/src/pyghex/CMakeLists.txt +++ b/bindings/python/src/pyghex/CMakeLists.txt @@ -52,7 +52,7 @@ set_target_properties(pyghex PROPERTIES target_link_libraries(pyghex PRIVATE ghex) ghex_link_to_oomph(ghex) -# If compiled with `make -j 6` this fails, because it is not fully build, there is a dependency that is not set correctly. +# Generate the typing stub files. nanobind_add_stub( pyghex_stub MODULE pyghex @@ -62,6 +62,7 @@ nanobind_add_stub( OUTPUT_PATH $ DEPENDS ghex ) +add_dependencies(pyghex_stub pyghex) # Set RPaths such that the python module is able to find libghex include(ghex_rpath) From f08e7a9a862e4f0ea6417be44ee806df729d8876 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 13:08:35 +0200 Subject: [PATCH 26/38] Not sure if this does the trick. --- cmake/ghex_python.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index e3abc2fc1..155e88704 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -8,7 +8,7 @@ if (GHEX_BUILD_PYTHON_BINDINGS) include(ghex_find_python_module) find_python_module(nanobind REQUIRED) - find_package(nanobind CONFIG REQUIRED HINTS "${PY_NANOBIND}/cmake") + find_package(nanobind CONFIG REQUIRED PATH "${PY_NANOBIND}/cmake") # Ask Python where it keeps its system (platform) packages. file(WRITE "${CMAKE_BINARY_DIR}/install-prefix" "${CMAKE_INSTALL_PREFIX}") From 8ccd52f4fed4ed1776e8e78d207fcbeb3596d5ba Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 13:11:00 +0200 Subject: [PATCH 27/38] Okay it is not that, because the CI once passed, but I do not understand why it no longer works. --- cmake/ghex_python.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index 155e88704..e3abc2fc1 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -8,7 +8,7 @@ if (GHEX_BUILD_PYTHON_BINDINGS) include(ghex_find_python_module) find_python_module(nanobind REQUIRED) - find_package(nanobind CONFIG REQUIRED PATH "${PY_NANOBIND}/cmake") + find_package(nanobind CONFIG REQUIRED HINTS "${PY_NANOBIND}/cmake") # Ask Python where it keeps its system (platform) packages. file(WRITE "${CMAKE_BINARY_DIR}/install-prefix" "${CMAKE_INSTALL_PREFIX}") From 54015fb4c58fbd3704cd8a8c1313c08354fa806f Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 14:01:01 +0200 Subject: [PATCH 28/38] Now the `REQUIRE` argument of the `find_python_module()` is respected. --- cmake/ghex_find_python_module.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/ghex_find_python_module.cmake b/cmake/ghex_find_python_module.cmake index 2dc2ff835..270464418 100644 --- a/cmake/ghex_find_python_module.cmake +++ b/cmake/ghex_find_python_module.cmake @@ -5,7 +5,7 @@ function(find_python_module module) if(NOT PY_${module_upper}) if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") - set(${module}_FIND_REQUIRED TRUE) + set(PY_${module}_FIND_REQUIRED TRUE) endif() # A module's location is usually a directory, but for binary modules From 00d504cc8dfa968ee756a38f08907b44dce0f9ef Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 14:50:58 +0200 Subject: [PATCH 29/38] Fixed the CI. GHEX needs to find the `nanobind` python package. Before it did not matter because the `REQUIRE` keyword was ignored, see commit `54015fb4c58`. Since the `nanobind` package is installed that thing was probably found. Thus this commit also installes the Python package. --- .github/workflows/CI.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6d71529cd..724d2125e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -123,7 +123,9 @@ jobs: - name: Install packages run: | apt update - apt-get -y install python3-dev python3-venv nanobind-dev ninja-build + # TODO: We need `python3-nanobind` but we do not need `nanobind-def`, in fact it should + # not even be used. Consider removing it. + apt-get -y install python3-dev python3-venv nanobind-dev ninja-build python3-nanobind - name: Clone w/ submodules uses: actions/checkout@v3 From 38c4df1391187d9dccd56a4bce0dcb7adce0ed4a Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 15:08:42 +0200 Subject: [PATCH 30/38] Only run `nanobind_add_stub()` if it is present also print version. --- bindings/python/src/pyghex/CMakeLists.txt | 24 ++++++++++++----------- cmake/ghex_python.cmake | 1 + 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/bindings/python/src/pyghex/CMakeLists.txt b/bindings/python/src/pyghex/CMakeLists.txt index 937b5b3e2..58747302a 100644 --- a/bindings/python/src/pyghex/CMakeLists.txt +++ b/bindings/python/src/pyghex/CMakeLists.txt @@ -52,17 +52,19 @@ set_target_properties(pyghex PROPERTIES target_link_libraries(pyghex PRIVATE ghex) ghex_link_to_oomph(ghex) -# Generate the typing stub files. -nanobind_add_stub( - pyghex_stub - MODULE pyghex - OUTPUT pyghex.pyi - RECURSIVE # We need to specify `RECURSIVE` to enable `OUTPUT_PATH`. - PYTHON_PATH $ - OUTPUT_PATH $ - DEPENDS ghex -) -add_dependencies(pyghex_stub pyghex) +# Generate the type stub if version permits it. +if (${nanobind_VERSION} VERSION_GREATER "2.3") + nanobind_add_stub( + pyghex_stub + MODULE pyghex + OUTPUT pyghex.pyi + RECURSIVE # We need to specify `RECURSIVE` to enable `OUTPUT_PATH`. + PYTHON_PATH $ + OUTPUT_PATH $ + DEPENDS ghex + ) + add_dependencies(pyghex_stub pyghex) +endif() # Set RPaths such that the python module is able to find libghex include(ghex_rpath) diff --git a/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index e3abc2fc1..c1a7cb311 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -9,6 +9,7 @@ if (GHEX_BUILD_PYTHON_BINDINGS) include(ghex_find_python_module) find_python_module(nanobind REQUIRED) find_package(nanobind CONFIG REQUIRED HINTS "${PY_NANOBIND}/cmake") + message(STATUS "`python-nanobind` version ${nanobind_VERSION} found.") # Ask Python where it keeps its system (platform) packages. file(WRITE "${CMAKE_BINARY_DIR}/install-prefix" "${CMAKE_INSTALL_PREFIX}") From e196a8668587d222e6a90eb41b2e7f18633dd33f Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 15:46:28 +0200 Subject: [PATCH 31/38] Fixed the dependecy. It was a typo. --- bindings/python/src/pyghex/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/python/src/pyghex/CMakeLists.txt b/bindings/python/src/pyghex/CMakeLists.txt index 58747302a..6a6289976 100644 --- a/bindings/python/src/pyghex/CMakeLists.txt +++ b/bindings/python/src/pyghex/CMakeLists.txt @@ -61,9 +61,8 @@ if (${nanobind_VERSION} VERSION_GREATER "2.3") RECURSIVE # We need to specify `RECURSIVE` to enable `OUTPUT_PATH`. PYTHON_PATH $ OUTPUT_PATH $ - DEPENDS ghex + DEPENDS pyghex ) - add_dependencies(pyghex_stub pyghex) endif() # Set RPaths such that the python module is able to find libghex From a1194e326ecc465d8289b3c0b306c1d1a3c30113 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Thu, 9 Apr 2026 15:46:44 +0200 Subject: [PATCH 32/38] The Python package now needs a higher nanobind version. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5070365e6..50971c2c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ build-backend = 'scikit_build_core.build' requires = [ 'cmake', - 'nanobind>=1.0.0', + 'nanobind>=2.0.0', 'scikit-build-core' ] From 7a6003de90254011345f6fcb3e13765e1b5f33b0 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Fri, 10 Apr 2026 08:46:51 +0200 Subject: [PATCH 33/38] Will solve that problem later. --- bindings/python/src/ghex/CMakeLists.txt | 2 ++ bindings/python/src/pyghex/CMakeLists.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/bindings/python/src/ghex/CMakeLists.txt b/bindings/python/src/ghex/CMakeLists.txt index 495619e11..fb1ac29ba 100644 --- a/bindings/python/src/ghex/CMakeLists.txt +++ b/bindings/python/src/ghex/CMakeLists.txt @@ -1,3 +1,5 @@ +# NOTE: These install commands only copy the `ghex` Python (wrapper) project. The actuall library, `pyghex` +# is copied in the `CMakeLists.txt` file of that folder. if (SKBUILD_PROJECT_NAME) # CMake driven by scikit-build-core install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ DESTINATION . FILES_MATCHING PATTERN "*.py") diff --git a/bindings/python/src/pyghex/CMakeLists.txt b/bindings/python/src/pyghex/CMakeLists.txt index 6a6289976..5363487fe 100644 --- a/bindings/python/src/pyghex/CMakeLists.txt +++ b/bindings/python/src/pyghex/CMakeLists.txt @@ -68,9 +68,11 @@ endif() # Set RPaths such that the python module is able to find libghex include(ghex_rpath) if (SKBUILD_PROJECT_NAME) + # TODO: Include the stub file here such that it is installed. set_target_properties(pyghex PROPERTIES INSTALL_RPATH "${rpath_origin_install_libdir}") install(TARGETS pyghex DESTINATION .) else() + # TODO: Include the stub file here such that it is installed. set_target_properties(pyghex PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") install(TARGETS pyghex DESTINATION ${GHEX_PYTHON_LIB_PATH}/ghex) endif() From e1c33ce1d351babcba0f4552f6bd7f4330072d04 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Fri, 10 Apr 2026 09:21:46 +0200 Subject: [PATCH 34/38] New way of finding `nanobind`. It now uses a fallback method, in which the Python package is prefered (and requiered in `pip` build mode, whcih is true since it is installed as a dependency) but otherwise it will fall back on the one that is installed on the system. However, I can not test it locally for one reason, but it should work on the CI. --- .github/workflows/CI.yml | 5 ++--- cmake/ghex_find_python_module.cmake | 2 +- cmake/ghex_python.cmake | 23 ++++++++++++++++++----- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 724d2125e..db527802a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -123,9 +123,8 @@ jobs: - name: Install packages run: | apt update - # TODO: We need `python3-nanobind` but we do not need `nanobind-def`, in fact it should - # not even be used. Consider removing it. - apt-get -y install python3-dev python3-venv nanobind-dev ninja-build python3-nanobind + # TODO: Use `python3-nanobind` instead of `nanobind-dev` once it is in the repo. + apt-get -y install python3-dev python3-venv nanobind-dev ninja-build - name: Clone w/ submodules uses: actions/checkout@v3 diff --git a/cmake/ghex_find_python_module.cmake b/cmake/ghex_find_python_module.cmake index 270464418..818a7cf8e 100644 --- a/cmake/ghex_find_python_module.cmake +++ b/cmake/ghex_find_python_module.cmake @@ -31,7 +31,7 @@ function(find_python_module module) else() set(HAVE_${module_upper} OFF CACHE INTERNAL "Python module available") endif() - endif(NOT PY_${module_upper}) + endif() find_package_handle_standard_args(PY_${module} DEFAULT_MSG PY_${module_upper}) endfunction(find_python_module) diff --git a/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index c1a7cb311..00505b488 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -1,15 +1,28 @@ include(GNUInstallDirs) if (GHEX_BUILD_PYTHON_BINDINGS) + include(ghex_find_python_module) find_package(Python 3 REQUIRED COMPONENTS Interpreter Development.Module) - set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") - include(ghex_find_python_module) - find_python_module(nanobind REQUIRED) - find_package(nanobind CONFIG REQUIRED HINTS "${PY_NANOBIND}/cmake") - message(STATUS "`python-nanobind` version ${nanobind_VERSION} found.") + # Look for the `nanobind` Python module. + find_python_module(nanobind) + + if (SKBUILD_PROJECT_NAME) + # Build as a Python package, `nanobind` is a buiild dependency, so it should be found. + if(NOT HAVE_NANOBIND) + message(FATAL_ERROR "Expected that the `nanobind` Python pakage was installed as dependency") + endif() + find_package(nanobind CONFIG REQUIRED HINTS "${PY_NANOBIND}/cmake") + elseif (HAVE_NANOBIND) + # Normal build and the `nanobind` Python package was found, use it. + find_package(nanobind CONFIG REQUIRED HINTS "${PY_NANOBIND}/cmake") + else() + # Normal build but no `nanobind` Python package was found, try to localize the one on the system. + # NOTE: The `CONFIG` is retained for compatibility with the old version, but maybe remove it. + find_package(nanobind CONFIG REQUIRED) + endif() # Ask Python where it keeps its system (platform) packages. file(WRITE "${CMAKE_BINARY_DIR}/install-prefix" "${CMAKE_INSTALL_PREFIX}") From e71a4d16eb857a78b53d0f9dd35d03de2aabebd0 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Fri, 10 Apr 2026 09:58:04 +0200 Subject: [PATCH 35/38] Let's do some debugging. --- cmake/ghex_python.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index 00505b488..65e408fed 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -10,19 +10,23 @@ if (GHEX_BUILD_PYTHON_BINDINGS) find_python_module(nanobind) if (SKBUILD_PROJECT_NAME) + message(STATUS "Building in pip mode.") # Build as a Python package, `nanobind` is a buiild dependency, so it should be found. if(NOT HAVE_NANOBIND) message(FATAL_ERROR "Expected that the `nanobind` Python pakage was installed as dependency") endif() find_package(nanobind CONFIG REQUIRED HINTS "${PY_NANOBIND}/cmake") elseif (HAVE_NANOBIND) + message(STATUS "Building in normal mode but use installed nanobind package.") # Normal build and the `nanobind` Python package was found, use it. find_package(nanobind CONFIG REQUIRED HINTS "${PY_NANOBIND}/cmake") else() + message(STATUS "Building in normal mode but use system nanobind.") # Normal build but no `nanobind` Python package was found, try to localize the one on the system. # NOTE: The `CONFIG` is retained for compatibility with the old version, but maybe remove it. find_package(nanobind CONFIG REQUIRED) endif() + message(STATUS "NANOBIND VERSION: ${nanobind_VERSION}") # Ask Python where it keeps its system (platform) packages. file(WRITE "${CMAKE_BINARY_DIR}/install-prefix" "${CMAKE_INSTALL_PREFIX}") From 7b986194109544fb421ba2abbd842b3de95ce2f1 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Fri, 10 Apr 2026 10:13:34 +0200 Subject: [PATCH 36/38] It seems that sometimes the `nanobind` version can not be determined. --- bindings/python/src/pyghex/CMakeLists.txt | 2 +- cmake/ghex_python.cmake | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/python/src/pyghex/CMakeLists.txt b/bindings/python/src/pyghex/CMakeLists.txt index 5363487fe..a421a61d5 100644 --- a/bindings/python/src/pyghex/CMakeLists.txt +++ b/bindings/python/src/pyghex/CMakeLists.txt @@ -53,7 +53,7 @@ target_link_libraries(pyghex PRIVATE ghex) ghex_link_to_oomph(ghex) # Generate the type stub if version permits it. -if (${nanobind_VERSION} VERSION_GREATER "2.3") +if (nanobind_VERSION AND (${nanobind_VERSION} VERSION_GREATER "2.3")) nanobind_add_stub( pyghex_stub MODULE pyghex diff --git a/cmake/ghex_python.cmake b/cmake/ghex_python.cmake index 65e408fed..530d7d4a9 100644 --- a/cmake/ghex_python.cmake +++ b/cmake/ghex_python.cmake @@ -26,7 +26,6 @@ if (GHEX_BUILD_PYTHON_BINDINGS) # NOTE: The `CONFIG` is retained for compatibility with the old version, but maybe remove it. find_package(nanobind CONFIG REQUIRED) endif() - message(STATUS "NANOBIND VERSION: ${nanobind_VERSION}") # Ask Python where it keeps its system (platform) packages. file(WRITE "${CMAKE_BINARY_DIR}/install-prefix" "${CMAKE_INSTALL_PREFIX}") From 6e9556593624f2b840d846b87194c4135c08db5f Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Fri, 10 Apr 2026 11:09:12 +0200 Subject: [PATCH 37/38] Let's hope that Mikael is right. --- bindings/python/src/pyghex/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/src/pyghex/CMakeLists.txt b/bindings/python/src/pyghex/CMakeLists.txt index a421a61d5..a01c82a06 100644 --- a/bindings/python/src/pyghex/CMakeLists.txt +++ b/bindings/python/src/pyghex/CMakeLists.txt @@ -53,7 +53,7 @@ target_link_libraries(pyghex PRIVATE ghex) ghex_link_to_oomph(ghex) # Generate the type stub if version permits it. -if (nanobind_VERSION AND (${nanobind_VERSION} VERSION_GREATER "2.3")) +if ((DEFINED nanobind_VERSION) AND ("${nanobind_VERSION}" VERSION_GREATER "2.3")) nanobind_add_stub( pyghex_stub MODULE pyghex From 4f52bcf2abd4274bacec5354ca621a032da3d5f0 Mon Sep 17 00:00:00 2001 From: "Philip Mueller, CSCS" Date: Fri, 10 Apr 2026 11:47:22 +0200 Subject: [PATCH 38/38] Fixed this merge. --- .../unstructured/communication_object.cpp | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/bindings/python/src/pyghex/unstructured/communication_object.cpp b/bindings/python/src/pyghex/unstructured/communication_object.cpp index 66de91433..ffb74687c 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); });