Skip to content

Commit 6d02d44

Browse files
Type conversions for Python Series constructor (#1737)
* Accept std::filesystem::path as path argument in constructor * Same trick for JSON config * Fix Nompi runs * Better distinguish JSON objects and MPI communicators * WIP: examples, testing, fixes * Same trick for Dataset() constructor * CI fixes * Fallback implementation for old compilers * Fix non-Python builds * Fixes * Ehh dont use the std::filesystem implementation by default * Type Check for filepath argument * Change CI options * Document Series constructor, add filepath option for config Also test * Document build option * Move helper to auxiliary header * Fix after rebase * Document helper
1 parent acd54b4 commit 6d02d44

File tree

16 files changed

+521
-236
lines changed

16 files changed

+521
-236
lines changed

.github/workflows/linux.yml

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,11 @@ jobs:
266266
env: {CXXFLAGS: -Werror, PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig}
267267
run: |
268268
share/openPMD/download_samples.sh build
269-
cmake -S . -B build \
270-
-DopenPMD_USE_PYTHON=ON \
271-
-DopenPMD_USE_MPI=ON \
272-
-DopenPMD_USE_HDF5=ON \
269+
cmake -S . -B build \
270+
-DopenPMD_USE_PYTHON=ON \
271+
-DopenPMD_USE_MPI=ON \
272+
-DopenPMD_USE_HDF5=ON \
273+
-DopenPMD_USE_FILESYSTEM_HEADER=ON \
273274
-DopenPMD_USE_INVASIVE_TESTS=ON
274275
cmake --build build --parallel 4
275276
ctest --test-dir build --output-on-failure
@@ -290,11 +291,12 @@ jobs:
290291
env: {CXXFLAGS: -Werror}
291292
run: |
292293
share/openPMD/download_samples.sh build
293-
cmake -S . -B build \
294-
-DopenPMD_USE_PYTHON=ON \
295-
-DopenPMD_USE_MPI=OFF \
296-
-DopenPMD_USE_HDF5=ON \
297-
-DopenPMD_USE_INVASIVE_TESTS=ON \
294+
cmake -S . -B build \
295+
-DopenPMD_USE_PYTHON=ON \
296+
-DopenPMD_USE_MPI=OFF \
297+
-DopenPMD_USE_HDF5=ON \
298+
-DopenPMD_USE_INVASIVE_TESTS=ON \
299+
-DopenPMD_USE_FILESYSTEM_HEADER=ON \
298300
-DPython_EXECUTABLE=$(which python3.10)
299301
cmake --build build --parallel 4
300302
ctest --test-dir build --output-on-failure
@@ -317,13 +319,14 @@ jobs:
317319
env: {CXXFLAGS: -Werror}
318320
run: |
319321
share/openPMD/download_samples.sh build
320-
cmake -S . -B build \
322+
cmake -S . -B build \
321323
-DCMAKE_CXX_FLAGS="-Wno-error=stringop-overread" \
322-
-DCMAKE_C_FLAGS="-Wno-error=stringop-overread" \
323-
-DopenPMD_USE_PYTHON=ON \
324-
-DopenPMD_USE_MPI=ON \
325-
-DopenPMD_USE_HDF5=ON \
326-
-DopenPMD_USE_ADIOS2=ON \
324+
-DCMAKE_C_FLAGS="-Wno-error=stringop-overread" \
325+
-DopenPMD_USE_PYTHON=ON \
326+
-DopenPMD_USE_MPI=ON \
327+
-DopenPMD_USE_HDF5=ON \
328+
-DopenPMD_USE_ADIOS2=ON \
329+
-DopenPMD_USE_FILESYSTEM_HEADER=ON \
327330
-DopenPMD_USE_INVASIVE_TESTS=ON
328331
cmake --build build --parallel 4
329332
ctest --test-dir build --output-on-failure

CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ option(openPMD_USE_INTERNAL_TOML11 "Use internally shipped toml11" ${op
151151

152152
option(openPMD_USE_INVASIVE_TESTS "Enable unit tests that modify source code" OFF)
153153
option(openPMD_USE_VERIFY "Enable internal VERIFY (assert) macro independent of build type" ON)
154+
option(openPMD_USE_FILESYSTEM_HEADER "Enable filesystem header. May be disabled for old compiler versions." OFF)
155+
mark_as_advanced(openPMD_USE_FILESYSTEM_HEADER)
154156

155157
set(CMAKE_CONFIGURATION_TYPES "Release;Debug;MinSizeRel;RelWithDebInfo")
156158
if(NOT CMAKE_BUILD_TYPE)
@@ -552,6 +554,7 @@ endif()
552554
if(openPMD_HAVE_PYTHON)
553555
add_library(openPMD.py MODULE
554556
src/binding/python/openPMD.cpp
557+
src/binding/python/auxiliary.cpp
555558
src/binding/python/Access.cpp
556559
src/binding/python/Attributable.cpp
557560
src/binding/python/BaseRecordComponent.cpp
@@ -732,6 +735,18 @@ if(openPMD_USE_INVASIVE_TESTS)
732735
target_compile_definitions(openPMD PRIVATE openPMD_USE_INVASIVE_TESTS=1)
733736
endif()
734737

738+
function(set_filesystem_header_for_target target)
739+
if(openPMD_USE_FILESYSTEM_HEADER)
740+
target_compile_definitions(${target} PRIVATE openPMD_USE_FILESYSTEM_HEADER=1)
741+
else()
742+
target_compile_definitions(${target} PRIVATE openPMD_USE_FILESYSTEM_HEADER=0)
743+
endif()
744+
endfunction()
745+
set_filesystem_header_for_target(openPMD)
746+
if(openPMD_HAVE_PYTHON)
747+
set_filesystem_header_for_target(openPMD.py)
748+
endif()
749+
735750
if(openPMD_BUILD_TESTING)
736751
# compile Catch2 implementation part separately
737752
add_library(CatchRunner ${_openpmd_lib_type}

docs/source/dev/buildoptions.rst

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,27 @@ Variants
99
The following options can be added to the ``cmake`` call to control features.
1010
CMake controls options with prefixed ``-D``, e.g. ``-DopenPMD_USE_MPI=OFF``:
1111

12-
============================== =============== ========================================================================
13-
CMake Option Values Description
14-
============================== =============== ========================================================================
15-
``openPMD_USE_MPI`` **AUTO**/ON/OFF Parallel, Multi-Node I/O for clusters
16-
``openPMD_USE_HDF5`` **AUTO**/ON/OFF HDF5 backend (``.h5`` files)
17-
``openPMD_USE_ADIOS2`` **AUTO**/ON/OFF ADIOS2 backend (``.bp`` files in BP3, BP4 or higher)
18-
``openPMD_USE_PYTHON`` **AUTO**/ON/OFF Enable Python bindings
19-
``openPMD_USE_INVASIVE_TESTS`` ON/**OFF** Enable unit tests that modify source code :sup:`1`
20-
``openPMD_USE_VERIFY`` **ON**/OFF Enable internal VERIFY (assert) macro independent of build type :sup:`2`
21-
``openPMD_INSTALL`` **ON**/OFF Add installation targets
22-
``openPMD_INSTALL_RPATH`` **ON**/OFF Add RPATHs to installed binaries
23-
``Python_EXECUTABLE`` (newest found) Path to Python executable
24-
============================== =============== ========================================================================
12+
================================= =============== ===============================================================================
13+
CMake Option Values Description
14+
================================= =============== ===============================================================================
15+
``openPMD_USE_MPI`` **AUTO**/ON/OFF Parallel, Multi-Node I/O for clusters
16+
``openPMD_USE_HDF5`` **AUTO**/ON/OFF HDF5 backend (``.h5`` files)
17+
``openPMD_USE_ADIOS2`` **AUTO**/ON/OFF ADIOS2 backend (``.bp`` files in BP3, BP4 or higher)
18+
``openPMD_USE_PYTHON`` **AUTO**/ON/OFF Enable Python bindings
19+
``openPMD_USE_INVASIVE_TESTS`` ON/**OFF** Enable unit tests that modify source code :sup:`1`
20+
``openPMD_USE_VERIFY`` **ON**/OFF Enable internal VERIFY (assert) macro independent of build type :sup:`2`
21+
``openPMD_INSTALL`` **ON**/OFF Add installation targets
22+
``openPMD_INSTALL_RPATH`` **ON**/OFF Add RPATHs to installed binaries
23+
``Python_EXECUTABLE`` (newest found) Path to Python executable
24+
``openPMD_USE_FILESYSTEM_HEADER`` ON/**OFF** In-/Exclude optional features implemented with ``<filesystem>`` header :sup:`3`
25+
================================= =============== ===============================================================================
2526

2627
:sup:`1` e.g. changes C++ visibility keywords, breaks MSVC
2728

2829
:sup:`2` this includes most pre-/post-condition checks, disabling without specific cause is highly discouraged
2930

31+
:sup:`3` currently only used for supporting ``pathlib.Path``-type arguments in the Python API; a manual fallback implementation is used otherwise
32+
3033

3134
Shared or Static
3235
----------------

examples/10_streaming_read.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#!/usr/bin/env python
2-
import json
32
import sys
43

54
import openpmd_api as io
@@ -18,8 +17,7 @@
1817
print("SST engine not available in ADIOS2.")
1918
sys.exit(0)
2019

21-
series = io.Series("simData.sst", io.Access_Type.read_linear,
22-
json.dumps(config))
20+
series = io.Series("simData.sst", io.Access_Type.read_linear, config)
2321

2422
# Read all available iterations and print electron position data.
2523
# Direct access to iterations is possible via `series.iterations`.

examples/10_streaming_write.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#!/usr/bin/env python
2-
import json
32
import sys
43

54
import numpy as np
@@ -21,8 +20,7 @@
2120

2221
# create a series and specify some global metadata
2322
# change the file extension to .json, .h5 or .bp for regular file writing
24-
series = io.Series("simData.sst", io.Access_Type.create,
25-
json.dumps(config))
23+
series = io.Series("simData.sst", io.Access_Type.create, config)
2624
series.set_author("Franz Poeschel <f.poeschel@hzdr.de>")
2725
series.set_software("openPMD-api-python-examples")
2826

examples/13_write_dynamic_configuration.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
#!/usr/bin/env python
2-
import json
3-
42
import numpy as np
53
import openpmd_api as io
64

@@ -19,6 +17,9 @@
1917
# Alternatively, the location of a JSON/TOML-file on the filesystem can
2018
# be passed by adding an at-sign `@` in front of the path
2119
# The format will then be recognized by filename extension, i.e. .json or .toml
20+
# In Python, normal Python dictionaries can also be used which will then be
21+
# converted via `json.dumps()` in the Series constructor
22+
# (see below for an example in terms of the Dataset constructor)
2223
2324
backend = "adios2"
2425
iteration_encoding = "group_based"
@@ -105,9 +106,6 @@ def main():
105106
'dataset': {
106107
'operators': []
107108
}
108-
},
109-
'adios1': {
110-
'dataset': {}
111109
}
112110
}
113111
config['adios2']['dataset'] = {
@@ -118,9 +116,6 @@ def main():
118116
}
119117
}]
120118
}
121-
config['adios1']['dataset'] = {
122-
'transform': 'blosc:compressor=zlib,shuffle=bit,lvl=1;nometa'
123-
}
124119

125120
temperature = iteration.meshes["temperature"]
126121
temperature.unit_dimension = {io.Unit_Dimension.theta: 1.0}
@@ -129,8 +124,7 @@ def main():
129124
# temperature has no x,y,z components, so skip the last layer:
130125
temperature_dataset = temperature
131126
# let's say we are in a 3x3 mesh
132-
dataset = io.Dataset(np.dtype("double"), [3, 3])
133-
dataset.options = json.dumps(config)
127+
dataset = io.Dataset(np.dtype("double"), [3, 3], config)
134128
temperature_dataset.reset_dataset(dataset)
135129
# temperature is constant
136130
local_data = np.arange(i * 9, (i + 1) * 9, dtype=np.dtype("double"))

examples/7_extended_write_serial.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
Authors: Axel Huebl, Fabian Koller
77
License: LGPLv3+
88
"""
9-
import json
10-
119
import numpy as np
1210
from openpmd_api import (Access, Dataset, Mesh_Record_Component, Series,
1311
Unit_Dimension)
@@ -104,7 +102,6 @@
104102
# before storing record data, you must specify the dataset once per
105103
# component this describes the datatype and shape of data as it should be
106104
# written to disk
107-
d = Dataset(partial_mesh.dtype, extent=[2, 5])
108105
dataset_config = {
109106
"adios2": {
110107
"dataset": {
@@ -117,7 +114,7 @@
117114
}
118115
}
119116
}
120-
d.options = json.dumps(dataset_config)
117+
d = Dataset(partial_mesh.dtype, extent=[2, 5], options=dataset_config)
121118
mesh["x"].reset_dataset(d)
122119

123120
electrons = cur_it.particles["electrons"]

examples/9_particle_write_serial.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,16 @@
66
Authors: Axel Huebl
77
License: LGPLv3+
88
"""
9-
import numpy as np
10-
from openpmd_api import (Access, Dataset, Mesh_Record_Component, Series,
11-
Unit_Dimension)
12-
13-
SCALAR = Mesh_Record_Component.SCALAR
9+
from pathlib import Path
1410

11+
import numpy as np
12+
from openpmd_api import Access, Dataset, Series, Unit_Dimension
1513

1614
if __name__ == "__main__":
1715
# open file for writing
18-
f = Series(
19-
"../samples/9_particle_write_serial_py.h5",
20-
Access.create
21-
)
16+
samples = Path("../samples")
17+
filename = "9_particle_write_serial_py.h5"
18+
f = Series(samples / filename, Access.create)
2219

2320
# all required openPMD attributes will be set to reasonable default values
2421
# (all ones, all zeros, empty strings,...)

include/openPMD/binding/python/Common.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
#include <pybind11/pybind11.h>
2727
#include <pybind11/stl.h>
2828
#include <pybind11/stl_bind.h>
29+
30+
#if openPMD_USE_FILESYSTEM_HEADER
31+
#include <pybind11/stl/filesystem.h>
32+
#endif
2933
// not yet used:
3034
// pybind11/functional.h // for std::function
3135

include/openPMD/binding/python/Mpi.hpp

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,25 @@ struct openPMD_PyMPICommObject
4343
};
4444
using openPMD_PyMPIIntracommObject = openPMD_PyMPICommObject;
4545

46-
inline std::variant<MPI_Comm, std::string>
46+
struct py_object_to_mpi_comm_error
47+
{
48+
enum class error_type
49+
{
50+
invalid_data,
51+
is_not_an_mpi_communicator
52+
};
53+
using error_type = error_type;
54+
55+
std::string error_msg;
56+
error_type type;
57+
58+
operator std::string() const
59+
{
60+
return error_msg;
61+
}
62+
};
63+
64+
inline std::variant<MPI_Comm, py_object_to_mpi_comm_error>
4765
pythonObjectAsMpiComm(pybind11::object &comm)
4866
{
4967
namespace py = pybind11;
@@ -55,10 +73,13 @@ pythonObjectAsMpiComm(pybind11::object &comm)
5573
//! - installed: include/mpi4py/mpi4py.MPI_api.h
5674
// if( import_mpi4py() < 0 ) { here be dragons }
5775

76+
using e = py_object_to_mpi_comm_error;
77+
using e_t = e::error_type;
78+
5879
if (comm.ptr() == Py_None)
59-
return {"MPI communicator cannot be None."};
80+
return e{"MPI communicator cannot be None.", e_t::invalid_data};
6081
if (comm.ptr() == nullptr)
61-
return {"MPI communicator is a nullptr."};
82+
return e{"MPI communicator is a nullptr.", e_t::invalid_data};
6283

6384
// check type string to see if this is mpi4py
6485
// __str__ (pretty)
@@ -68,15 +89,18 @@ pythonObjectAsMpiComm(pybind11::object &comm)
6889
py::str const comm_pystr = py::repr(comm);
6990
std::string const comm_str = comm_pystr.cast<std::string>();
7091
if (comm_str.substr(0, 12) != std::string("<mpi4py.MPI."))
71-
return {"comm is not an mpi4py communicator: " + comm_str};
92+
return e{
93+
"comm is not an mpi4py communicator: " + comm_str,
94+
e_t::is_not_an_mpi_communicator};
7295
// only checks same layout, e.g. an `int` in `PyObject` could pass this
7396
if (!py::isinstance<py::class_<openPMD_PyMPIIntracommObject> >(
7497
comm.get_type()))
7598
// TODO add mpi4py version from above import check to error message
76-
return {
99+
return e{
77100
"comm has unexpected type layout in " + comm_str +
78-
" (Mismatched MPI at compile vs. runtime? "
79-
"Breaking mpi4py release?)"};
101+
" (Mismatched MPI at compile vs. runtime? "
102+
"Breaking mpi4py release?)",
103+
e_t::invalid_data};
80104

81105
// todo other possible implementations:
82106
// - pyMPI (inactive since 2008?): import mpi; mpi.WORLD
@@ -87,12 +111,13 @@ pythonObjectAsMpiComm(pybind11::object &comm)
87111
&((openPMD_PyMPIIntracommObject *)(comm.ptr()))->ob_mpi;
88112

89113
if (PyErr_Occurred())
90-
return {"MPI communicator access error."};
114+
return e{"MPI communicator access error.", e_t::invalid_data};
91115
if (mpiCommPtr == nullptr)
92116
{
93-
return {
117+
return e{
94118
"MPI communicator cast failed. "
95-
"(Mismatched MPI at compile vs. runtime?)"};
119+
"(Mismatched MPI at compile vs. runtime?)",
120+
e_t::invalid_data};
96121
}
97122
return {*mpiCommPtr};
98123
}

0 commit comments

Comments
 (0)