1818 * and the GNU Lesser General Public License along with openPMD-api.
1919 * If not, see <http://www.gnu.org/licenses/>.
2020 */
21+ #include < limits>
22+ #include < pybind11/detail/common.h>
2123#include < pybind11/numpy.h>
2224#include < pybind11/pybind11.h>
2325#include < pybind11/stl.h>
2426
2527#include " openPMD/DatatypeHelpers.hpp"
2628#include " openPMD/Error.hpp"
29+ #include " openPMD/RecordComponent.hpp"
2730#include " openPMD/Series.hpp"
2831#include " openPMD/backend/BaseRecordComponent.hpp"
2932
4043#include < exception>
4144#include < iostream>
4245#include < sstream>
46+ #include < stdexcept>
4347#include < string>
4448#include < tuple>
4549#include < type_traits>
@@ -111,14 +115,48 @@ inline std::tuple<Offset, Extent, std::vector<bool>> parseTupleSlices(
111115 py::slice slice = py::cast<py::slice>(slices[i]);
112116
113117 size_t start, stop, step, slicelength;
118+ auto mocked_extent = full_extent.at (curAxis);
119+ // py::ssize_t is a signed type, so we will need to use another
120+ // magic number for JOINED_DIMENSION in this computation, since the
121+ // C++ API's JOINED_DIMENSION would be interpreted as a negative
122+ // index
123+ bool undo_mocked_extent = false ;
124+ constexpr auto PYTHON_JOINED_DIMENSION =
125+ std::numeric_limits<py::ssize_t >::max () - 1 ;
126+ if (mocked_extent == Dataset::JOINED_DIMENSION)
127+ {
128+ undo_mocked_extent = true ;
129+ mocked_extent = PYTHON_JOINED_DIMENSION;
130+ }
114131 if (!slice.compute (
115- full_extent.at (curAxis),
116- &start,
117- &stop,
118- &step,
119- &slicelength))
132+ mocked_extent, &start, &stop, &step, &slicelength))
120133 throw py::error_already_set ();
121134
135+ if (undo_mocked_extent)
136+ {
137+ // do the same calculation again, but with another global extent
138+ // (that is not smaller than the previous in order to avoid
139+ // cutting off the range)
140+ // this is to avoid the unlikely case
141+ // that the mocked alternative value is actually the intended
142+ // one
143+ size_t start2, stop2, step2, slicelength2;
144+ if (!slice.compute (
145+ mocked_extent + 1 ,
146+ &start2,
147+ &stop2,
148+ &step2,
149+ &slicelength2))
150+ throw py::error_already_set ();
151+ if (slicelength == slicelength2)
152+ {
153+ // slicelength was given as an absolute value and
154+ // accidentally hit our mocked value
155+ // --> keep that value
156+ undo_mocked_extent = false ;
157+ }
158+ }
159+
122160 // TODO PySlice_AdjustIndices: Python 3.6.1+
123161 // Adjust start/end slice indices assuming a sequence of the
124162 // specified length. Out of bounds indices are clipped in a
@@ -132,7 +170,10 @@ inline std::tuple<Offset, Extent, std::vector<bool>> parseTupleSlices(
132170
133171 // verified for size later in C++ API
134172 offset.at (curAxis) = start;
135- extent.at (curAxis) = slicelength; // stop - start;
173+ extent.at (curAxis) =
174+ undo_mocked_extent && slicelength == PYTHON_JOINED_DIMENSION
175+ ? Dataset::JOINED_DIMENSION
176+ : slicelength; // stop - start;
136177
137178 continue ;
138179 }
@@ -187,6 +228,59 @@ inline std::tuple<Offset, Extent, std::vector<bool>> parseTupleSlices(
187228 return std::make_tuple (offset, extent, flatten);
188229}
189230
231+ inline std::tuple<Offset, Extent, std::vector<bool >> parseJoinedTupleSlices (
232+ uint8_t const ndim,
233+ Extent const &full_extent,
234+ py::tuple const &slices,
235+ size_t joined_dim,
236+ py::array const &a)
237+ {
238+
239+ std::vector<bool > flatten;
240+ Offset offset;
241+ Extent extent;
242+ std::tie (offset, extent, flatten) =
243+ parseTupleSlices (ndim, full_extent, slices);
244+ for (size_t i = 0 ; i < ndim; ++i)
245+ {
246+ if (offset.at (i) != 0 )
247+ {
248+ throw std::runtime_error (
249+ " Joined array: Cannot use non-zero offset in store_chunk "
250+ " (offset[" +
251+ std::to_string (i) + " ] = " + std::to_string (offset[i]) + " )." );
252+ }
253+ if (flatten.at (i))
254+ {
255+ throw std::runtime_error (
256+ " Flattened slices unimplemented for joined arrays." );
257+ }
258+
259+ if (i == joined_dim)
260+ {
261+ if (extent.at (i) == 0 || extent.at (i) == Dataset::JOINED_DIMENSION)
262+ {
263+ extent[i] = a.shape ()[i];
264+ }
265+ }
266+ else
267+ {
268+ if (extent.at (i) != full_extent.at (i))
269+ {
270+ throw std::runtime_error (
271+ " Joined array: Must use full extent in store_chunk for "
272+ " non-joined dimension "
273+ " (local_extent[" +
274+ std::to_string (i) + " ] = " + std::to_string (extent[i]) +
275+ " != global_extent[" + std::to_string (i) +
276+ " ] = " + std::to_string (full_extent[i]) + " )." );
277+ }
278+ }
279+ }
280+ offset.clear ();
281+ return std::make_tuple (offset, extent, flatten);
282+ }
283+
190284/* * Check an array is a contiguous buffer
191285 *
192286 * Required are contiguous buffers for store and load
@@ -388,8 +482,17 @@ store_chunk(RecordComponent &r, py::array &a, py::tuple const &slices)
388482 Offset offset;
389483 Extent extent;
390484 std::vector<bool > flatten;
391- std::tie (offset, extent, flatten) =
392- parseTupleSlices (ndim, full_extent, slices);
485+ if (auto joined_dimension = r.joinedDimension ();
486+ joined_dimension.has_value ())
487+ {
488+ std::tie (offset, extent, flatten) = parseJoinedTupleSlices (
489+ ndim, full_extent, slices, *joined_dimension, a);
490+ }
491+ else
492+ {
493+ std::tie (offset, extent, flatten) =
494+ parseTupleSlices (ndim, full_extent, slices);
495+ }
393496
394497 store_chunk (r, a, offset, extent, flatten);
395498}
0 commit comments