DNDSR 0.2.1
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
MPI_bind.cpp
Go to the documentation of this file.
1#include "MPI.hpp"
2
3#include "MPI_bind.hpp"
4
5#include <pybind11/stl.h>
6#include <pybind11/numpy.h>
7
8namespace DNDS
9{
10 void pybind11_MPIInfo(py::module_ &m)
11 {
12 py_class_ssp<MPIInfo>(m, "MPIInfo")
13 .def(py::init<>())
14 // Python side passes an opaque `uintptr_t` handle produced by
15 // `MPI_Comm_c2f`/ctypes; the reverse conversion back into
16 // `MPI_Comm` is intentionally an integer-to-pointer reinterpretation.
17 .def(py::init([](uintptr_t pComm)
18 // NOLINTNEXTLINE(performance-no-int-to-ptr)
19 { return std::make_unique<MPIInfo>(MPI_Comm(pComm)); }))
20 .def("setWorld", &MPIInfo::setWorld)
21 .def_readonly("rank", &MPIInfo::rank)
22 .def_readonly("size", &MPIInfo::size)
23 .def("comm", [](const MPIInfo &self)
24 { return size_t(self.comm); })
25 .def("equals", &MPIInfo::operator==);
26 }
27}
28
29namespace DNDS::MPI
30{
31 void pybind11_Init_thread(py::module_ &m)
32 {
33 auto m_MPI = m.def_submodule("MPI");
34 m_MPI.def(
35 "Init_thread",
36 [](const std::vector<std::string> &pArgv)
37 {
38 // std::vector<const char *> argStarts;
39 // for (auto &v : pArgv)
40 // argStarts.push_back(v.c_str());
41 // int argn = argStarts.size();
42 // auto argv = argStarts.data();
43 // auto ret = Init_thread(&argn, const_cast<char ***>(&argv));
44 // //! Warning: assuming mpi won't touch anything
45 // return std::make_tuple(ret, pArgv);
46
47 // ! a complete version
48
49 int initial_argc = static_cast<int>(pArgv.size());
50 int initial_argc_mine = initial_argc;
51
52 // NOLINTBEGIN(cppcoreguidelines-owning-memory): MPI_Init_thread
53 // requires a mutable `char ***argv` with ownership retained by
54 // the caller through the lifetime of Init/Finalize. Smart
55 // pointers would require reshaping the MPI-C API contract.
56 // Paired `delete[]` at the end of this block.
57 //
58 // Create an array of pointers to C-style strings:
59 char **argv_array = new char *[initial_argc + 1]; // +1 for NULL terminator
60 char **argv_array_mine = argv_array;
61
62 // Allocate memory and copy each string into a modifiable buffer
63 for (int i = 0; i < initial_argc; ++i)
64 {
65 const std::string &s = pArgv[i];
66 size_t len = s.length();
67 char *cstr = new char[len + 1]; // Allocate space for '\0'
68 strcpy(cstr, s.c_str()); // Copy the string
69 argv_array[i] = cstr;
70 }
71 argv_array[initial_argc] = nullptr; // NULL terminator
72
73 int *pargc = &initial_argc;
74 char ***pargv = &argv_array;
75
76 auto ret = Init_thread(pargc, pargv);
77
78 // Capture the modified arguments into output_args:
79 std::vector<std::string> pArgvOut;
80 pArgvOut.reserve(*pargc);
81 for (int i = 0; i < *pargc; ++i)
82 pArgvOut.emplace_back(argv_array[i]);
83
84 // Cleanup all dynamically allocated memory
85 // Note: Even if MPI changes entries in the array, our pointers still point to
86 // our original allocations (assuming MPI doesn't reallocate the entire array)
87 for (int i = 0; i <= initial_argc_mine; ++i)
88 delete[] argv_array_mine[i]; // Free each string buffer
89 delete[] argv_array_mine; // Free the pointer array
90 // NOLINTEND(cppcoreguidelines-owning-memory)
91
92 return std::make_tuple(ret, pArgvOut);
93 });
94 m_MPI.def(
95 "Finalize",
96 []()
97 {
98 return MPI::Finalize();
99 });
100 m_MPI.def("GetMPIThreadLevel", &GetMPIThreadLevel);
101 }
102
103 void pybind11_MPI_Operations(py::module_ &m)
104 {
105 auto m_MPI = m.def_submodule("MPI");
106 m_MPI.def(
107 "Allreduce",
108 [](const py::buffer &py_sendbuf, const py::buffer &py_recvbuf, const std::string &op, const MPIInfo &mpi)
109 {
110 auto send_info = py_sendbuf.request(false);
111 auto recv_info = py_recvbuf.request(true);
112
113 DNDS_assert_info(recv_info.format == send_info.format,
114 fmt::format("send and recv buffer format incompatible: [{}], [{}]",
115 send_info.format, recv_info.format));
116
117 MPI_Datatype datatype = py_get_buffer_basic_mpi_datatype(send_info);
118 DNDS_assert(datatype != MPI_DATATYPE_NULL);
119 MPI_Op mpi_op = py_get_simple_mpi_op_by_name(op);
120
121 auto [count_s, send_style] = py_buffer_get_contigious_size(send_info);
122 auto [count_r, recv_style] = py_buffer_get_contigious_size(send_info);
123 DNDS_assert_info(count_r >= count_s, "receive buffer size not enough");
124
125 MPI_int err = Allreduce(send_info.ptr, recv_info.ptr, count_s,
126 datatype, mpi_op, mpi.comm);
127 },
128 py::arg("send"), py::arg("recv"), py::arg("op"), py::arg("mpi"));
129 }
130}
131
132namespace DNDS::Debug
133{
134 // bool IsDebugged();
135 // void MPIDebugHold(const MPIInfo &mpi);
136
137 void pybind11_Debug(py::module_ &m)
138 {
139 auto m_Debug = m.def_submodule("Debug");
140 m_Debug.def("IsDebugged", &IsDebugged);
141 m_Debug.def("MPIDebugHold", &MPIDebugHold);
142 }
143}
144
145namespace DNDS
146{
154}
#define DNDS_assert_info(expr, info)
Debug-only assertion with an extra std::string info message.
Definition Errors.hpp:117
#define DNDS_assert(expr)
Debug-only assertion (compiled out when DNDS_NDEBUG is defined). Prints the expression + file/line + ...
Definition Errors.hpp:112
MPI wrappers: MPIInfo, collective operations, type mapping, CommStrategy.
pybind11 bindings for the DNDS MPI wrappers (MPIInfo, CommStrategy, buffer-protocol datatype helpers)...
Eigen::Matrix< real, 3, 3 > m
bool IsDebugged()
Whether the current process is running under a debugger. Implemented via /proc/self/status TracerPid ...
Definition MPI.cpp:34
void MPIDebugHold(const MPIInfo &mpi)
If isDebugging is set, block every rank in a busy-wait loop so the user can attach a debugger and ins...
Definition MPI.cpp:58
void pybind11_Debug(py::module_ &m)
Definition MPI_bind.cpp:137
void pybind11_Init_thread(py::module_ &m)
Definition MPI_bind.cpp:31
int Finalize()
Release DNDSR-registered MPI resources then call MPI_Finalize.
Definition MPI.hpp:570
void pybind11_MPI_Operations(py::module_ &m)
Definition MPI_bind.cpp:103
MPI_int Allreduce(const void *sendbuf, void *recvbuf, MPI_int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
Wrapper over MPI_Allreduce.
Definition MPI.cpp:202
MPI_int Init_thread(int *argc, char ***argv)
Initialise MPI with thread support, honouring the DNDS_DISABLE_ASYNC_MPI environment override.
Definition MPI.hpp:534
int GetMPIThreadLevel()
Return the MPI thread-support level the current process was initialised with.
Definition MPI.hpp:513
the host side operators are provided as implemented
MPI_Op py_get_simple_mpi_op_by_name(const std::string &op)
Definition MPI_bind.hpp:43
py::classh< T > py_class_ssp
std::tuple< ssize_t, char > py_buffer_get_contigious_size(const py::buffer_info &info)
void pybind11_MPIInfo(py::module_ &m)
Definition MPI_bind.cpp:10
MPI_Datatype py_get_buffer_basic_mpi_datatype(const py::buffer_info &info)
Definition MPI_bind.hpp:13
void pybind11_bind_MPI_All(py::module_ &m)
Definition MPI_bind.cpp:147
int MPI_int
MPI counterpart type for MPI_int (= C int). Used for counts and ranks in MPI calls.
Definition MPI.hpp:54
Lightweight bundle of an MPI communicator and the calling rank's coordinates.
Definition MPI.hpp:231
int size
Number of ranks in comm (-1 until initialised).
Definition MPI.hpp:237
int rank
This rank's 0-based index within comm (-1 until initialised).
Definition MPI.hpp:235
MPI_Comm comm
The underlying MPI communicator handle.
Definition MPI.hpp:233
void setWorld()
Initialise the object to MPI_COMM_WORLD. Requires MPI_Init to have run.
Definition MPI.hpp:258
real err