Repository navigation
Add submodule mpi - #40
wdeconinck wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds MPI communicator management and interoperability to the atlas4py Python bindings.
Changes:
- Adds communicator bindings, registration, scoping, splitting, and mpi4py conversion.
- Adds Atlas version parsing and conditional compilation support.
- Adds MPI binding tests.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_bindings.py |
Tests MPI APIs and interoperability. |
src/atlas4py/mpi/_mpi.hpp |
Declares MPI wrappers. |
src/atlas4py/mpi/_mpi.cpp |
Implements and binds MPI functionality. |
src/atlas4py/CMakeLists.txt |
Builds the MPI binding sources. |
src/atlas4py/cmake/atlas4py_parse_version.cmake |
Parses version components. |
src/atlas4py/cmake/atlas4py_add_atlas.cmake |
Integrates Atlas version detection. |
src/atlas4py/_atlas4py.cpp |
Registers the MPI submodule. |
Suppressed comments (1)
tests/test_bindings.py:497
- The repository runs this suite with plain
pytestand has nompiexec/mpiruntest invocation, soworld.sizeis one andcoloris always zero here. The test only covers the degenerate serial split and cannot detect multi-rank grouping/collective failures. Add an MPI-launched multi-process case with ranks assigned to both colors.
color = world.rank % 2
split_comm = world.split(color, "split_comm")
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
ca9589a to
cb9e259
Compare
tehrengruber
left a comment
There was a problem hiding this comment.
First round. Fable review otw.
| atlas4py.Config.from_file(test_config_yaml, format="toml") | ||
|
|
||
| assert "does not select a parser or validate the file contents" in atlas4py.Config.from_file.__doc__ | ||
|
|
There was a problem hiding this comment.
Yes that makes sense. We're modularising library code, so then also test code.
| @@ -0,0 +1,99 @@ | |||
| # This file defines a function to parse version strings and export version components as CMake variables. | |||
There was a problem hiding this comment.
No strong objection, but until there is a user that needs this I would KISS and rather only support the latest atlas api in the bindings so that no compatibility logic is required.
There was a problem hiding this comment.
So you mean that we should bump the required atlas version instead? I'm OK with that of course!
Let's make that a new policy from now on then.
| @@ -0,0 +1,274 @@ | |||
|
|
|||
There was a problem hiding this comment.
Should we get rid of the underscores in the filename? For _atlas4py.cpp I believe the reason is that this is also the name of the python module which (at least somewhat) makes sense.
There was a problem hiding this comment.
Sounds sensible indeed.
tehrengruber-ai
left a comment
There was a problem hiding this comment.
Nice work — the interop design is sound: to_int() maps to eckit::mpi::Comm::communicator(), which is the Fortran handle, exactly what mpi4py's toint()/fromint() use, so the round-trip is correct. The duck-typed toint/to_int dispatch is a good touch.
Four correctness issues below. I have some further notes on the CMake version plumbing, exception types, GIL handling around blocking MPI calls, and test isolation, but those are minor — happy to follow up separately.
| .def( "__repr__", []( const mpi::Scope& scope ) { | ||
| return "atlas4py.mpi.Scope(name=" + atlas::mpi::comm().name() + ")"; | ||
| }, "Return a string representation of the MPI scope." ); |
There was a problem hiding this comment.
Scope.__repr__ reports the wrong name.
The scope parameter is unused — this prints the current default comm rather than the scope's own name. Scope never stores its name, so nested scopes report the innermost one:
with mpi.Scope("scope 1") as s1:
with mpi.Scope("scope 2"):
repr(s1) # -> "atlas4py.mpi.Scope(name=scope 2)"test_scope_repr_and_str passes only because it exercises a single active scope.
Suggest storing name_ in Scope (see the related comment on pop(), which needs it anyway) and printing that.
| static void pop() { | ||
| #if ATLAS4PY_ATLAS_VERSION_AT_LEAST(0,46,0) | ||
| atlas::mpi::scope::pop(); | ||
| #else | ||
| atlas::mpi::pop(); | ||
| #endif | ||
| } |
There was a problem hiding this comment.
pop() uses the unchecked overload, which can silently pop the wrong scope.
atlas exposes both pop() and pop(name) — and atlas::mpi::Scope itself deliberately uses the checked one:
// atlas/parallel/mpi/mpi.h
struct Scope {
Scope(std::string_view name) : name_(name) { push(name_); }
~Scope() { pop(name_); } // verifies the name matches
};Python object destruction is not guaranteed to be LIFO, so an out-of-order collection pops someone else's scope instead of raising:
s1 = mpi.Scope("a") # push a
s2 = mpi.Scope("b") # push b
del s1 # pops "b"Related, in the constructor at lines 172-174: the push happens at construction rather than in __enter__, so mpi.Scope("world") mutates global state the moment it is constructed and unwinds only at GC. test_scope_repr_and_str does exactly this, leaving "world" pushed for as long as the object survives.
Two fixes worth considering: store the name and pass it to pop, and/or move the push into __enter__.
| static const Comm& SELF(); | ||
|
|
||
| private: | ||
| const eckit::mpi::Comm& comm_; |
There was a problem hiding this comment.
This reference dangles after delete_comm.
mpi::Comm holds const eckit::mpi::Comm&, but eckit documents deleteComm as "Unregister and delete specific comm", and Comm::free() as "After calling this method, the communicator should not be used again". Any live Python Comm pointing at that object is then dangling. Same applies to unregister_comm on eckit >= 2.0.
The new test does this:
# tests/test_bindings.py, test_mpi_comm_split
split_comm_by_name = mpi.Comm("split_comm")
...
mpi.delete_comm("split_comm") # split_comm and split_comm_by_name now dangleIt survives today only because name_ / size_ / rank_ were already cached before the delete — to_int() on either object afterwards is a use-after-free.
Storing the name and resolving through atlas::mpi::comm(name_) on each access would make this safe (at the cost of a lookup), or the lifetime constraint needs documenting loudly.
| if ( register_if_not_found == RegisterIfNotFound::yes ) { | ||
| std::string name = comm_name_from_int(comm_as_int); | ||
| eckit::mpi::addComm( name.c_str(), comm_as_int ); | ||
| return atlas::mpi::comm( name ); | ||
| } |
There was a problem hiding this comment.
Unknown integer handles are registered without validation.
Comm(int) always passes RegisterIfNotFound::yes, so Comm(9999) registers handle 9999 as "int.9999" with no check that it is a real communicator. Under a real MPI build eckit will convert and use that handle (MPI_Comm_f2c, then MPI_Comm_size), which aborts.
test_mpi_scope's Mock_mpi4py_MPI_Comm(9999) and test_mpi_comm_registration's handle 6 pass only against the serial stub — they would fail under mpirun.
Worth either validating the handle before registering, or restricting those tests to serial builds so the suite stays runnable under a real MPI.
Minor, same block: the RegisterIfNotFound::no branch is never reached — no caller passes it. And at line 59, for( std::string name : ... ) copies each string; const auto& would do.
This pull request introduces a new
atlas4py.mpiPython submodule, providing comprehensive MPI communicator management from eckit and interoperability withmpi4pyand other MPI libraries. It includes the C++ implementation, Python bindings, CMake integration, version parsing utilities, and extensive test coverage. The changes are grouped below by theme.New MPI Python Submodule and Bindings
mpisubmodule toatlas4py, exposing MPI communicator management, registration, and context management in Python, with support for interoperability withmpi4pyand handling communicators as integer handles or objects.CMake Integration and Version Utilities
src/atlas4py/cmake/atlas4py_parse_version.cmake). This is needed for ensuring forward/backward compatibility with different atlas versions for an API change.src/atlas4py/CMakeLists.txt).Testing
mpi4py, context management withScope, and communicator splitting (tests/test_bindings.py).These changes provide robust MPI support in
atlas4py, enable seamless integration with existing Python MPI workflows, and ensure correctness and maintainability through improved build tooling and thorough testing.