Skip to content

Commit 31ea01e

Browse files
committed
Add Python bindings for RecordComponent::makeEmpty()
See also #529 Add a non-template overload to RecordComponent::makeEmpty Add tests for previous commit Refactor python binding to use non-template version of makeEmpty Add python tests for make_empty Add an overload to RecordComponent.make_empty for numpy datatypes Add tests for numpy datatypes Fix datatype tests Only test fixed-sized types
1 parent 4a9ff7f commit 31ea01e

5 files changed

Lines changed: 222 additions & 0 deletions

File tree

include/openPMD/RecordComponent.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ class RecordComponent : public BaseRecordComponent
119119
template< typename T >
120120
RecordComponent& makeEmpty( uint8_t dimensions );
121121

122+
/**
123+
* @brief Non-template overload of RecordComponent::makeEmpty().
124+
* Uses the passed openPMD datatype to determine the template parameter.
125+
*
126+
* @param dimensions
127+
* @return RecordComponent&
128+
*/
129+
RecordComponent& makeEmpty( Datatype, uint8_t dimensions );
130+
122131
/** Load and allocate a chunk of data
123132
*
124133
* Set offset to {0u} and extent to {-1u} for full selection.

src/RecordComponent.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,32 @@ RecordComponent::getExtent() const
7777
return m_dataset->extent;
7878
}
7979

80+
namespace detail
81+
{
82+
struct MakeEmpty
83+
{
84+
template< typename T >
85+
RecordComponent& operator()( RecordComponent & rc, uint8_t dimensions )
86+
{
87+
return rc.makeEmpty< T >( dimensions );
88+
}
89+
90+
template< unsigned int N >
91+
RecordComponent& operator()( RecordComponent &, uint8_t )
92+
{
93+
throw std::runtime_error(
94+
"RecordComponent::makeEmpty: Unknown datatype." );
95+
}
96+
};
97+
}
98+
99+
RecordComponent&
100+
RecordComponent::makeEmpty( Datatype dt, uint8_t dimensions )
101+
{
102+
static detail::MakeEmpty me;
103+
return switchType< RecordComponent & >( dt, me, *this, dimensions );
104+
}
105+
80106
RecordComponent&
81107
RecordComponent::makeEmpty( Dataset d )
82108
{

src/binding/python/RecordComponent.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,20 @@ void init_RecordComponent(py::module &m) {
528528
py::arg("value"))
529529
.def("make_constant", &RecordComponent::makeConstant<bool>,
530530
py::arg("value"))
531+
.def("make_empty",
532+
[]( RecordComponent & rc, Datatype dt, uint8_t dimensionality )
533+
{
534+
return rc.makeEmpty( dt, dimensionality );
535+
},
536+
py::arg("datatype"), py::arg("dimensionality"))
537+
.def("make_empty",
538+
[](
539+
RecordComponent & rc,
540+
pybind11::dtype const dt,
541+
uint8_t dimensionality )
542+
{
543+
return rc.makeEmpty( dtype_from_numpy( dt ), dimensionality );
544+
})
531545

532546
// TODO if we also want to support scalar arrays, we have to switch
533547
// py::array for py::buffer as in Attributable

test/SerialIOTest.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,19 @@ empty_dataset_test( std::string file_ending )
4949
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_int" ];
5050
auto makeEmpty_dim_7_long =
5151
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_bool" ];
52+
auto makeEmpty_dim_7_int_alt =
53+
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_int_alt" ];
54+
auto makeEmpty_dim_7_long_alt =
55+
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_bool_alt" ];
5256
auto makeEmpty_resetDataset_dim3 =
5357
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_resetDataset_dim3" ];
5458
auto makeEmpty_resetDataset_dim3_notallzero =
5559
series.iterations[ 1 ]
5660
.meshes[ "rho" ][ "makeEmpty_resetDataset_dim3_notallzero" ];
5761
makeEmpty_dim_7_int.makeEmpty< int >( 7 );
5862
makeEmpty_dim_7_long.makeEmpty< long >( 7 );
63+
makeEmpty_dim_7_int_alt.makeEmpty( Datatype::INT, 7 );
64+
makeEmpty_dim_7_long_alt.makeEmpty( Datatype::LONG, 7 );
5965
makeEmpty_resetDataset_dim3.resetDataset(
6066
Dataset( Datatype::LONG, Extent( 3, 0 ) ) );
6167
makeEmpty_resetDataset_dim3_notallzero.resetDataset(
@@ -70,6 +76,10 @@ empty_dataset_test( std::string file_ending )
7076
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_int" ];
7177
auto makeEmpty_dim_7_long =
7278
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_bool" ];
79+
auto makeEmpty_dim_7_int_alt =
80+
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_int_alt" ];
81+
auto makeEmpty_dim_7_long_alt =
82+
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_bool_alt" ];
7383
auto makeEmpty_resetDataset_dim3 =
7484
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_resetDataset_dim3" ];
7585
auto makeEmpty_resetDataset_dim3_notallzero =
@@ -83,6 +93,14 @@ empty_dataset_test( std::string file_ending )
8393
REQUIRE(makeEmpty_dim_7_long.getExtent() == Extent(7, 0));
8494
REQUIRE(isSame(makeEmpty_dim_7_long.getDatatype(), determineDatatype< long >()));
8595

96+
REQUIRE(makeEmpty_dim_7_int_alt.getDimensionality() == 7);
97+
REQUIRE(makeEmpty_dim_7_int_alt.getExtent() == Extent(7, 0));
98+
REQUIRE(isSame(makeEmpty_dim_7_int_alt.getDatatype(), determineDatatype< int >()));
99+
100+
REQUIRE(makeEmpty_dim_7_long_alt.getDimensionality() == 7);
101+
REQUIRE(makeEmpty_dim_7_long_alt.getExtent() == Extent(7, 0));
102+
REQUIRE(isSame(makeEmpty_dim_7_long_alt.getDatatype(), determineDatatype< long >()));
103+
86104
REQUIRE(makeEmpty_resetDataset_dim3.getDimensionality() == 3);
87105
REQUIRE(makeEmpty_resetDataset_dim3.getExtent() == Extent(3, 0));
88106
REQUIRE(isSame(makeEmpty_resetDataset_dim3.getDatatype(), Datatype::LONG));

test/python/unittest/API/APITest.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,161 @@ def testConstantRecords(self):
413413
if api.variants[b] is True and b in backend_filesupport:
414414
self.makeConstantRoundTrip(backend_filesupport[b])
415415

416+
def makeEmptyRoundTrip(self, file_ending):
417+
# write
418+
series = api.Series(
419+
"unittest_py_empty_API." + file_ending,
420+
api.Access_Type.create
421+
)
422+
423+
ms = series.iterations[0].meshes
424+
SCALAR = api.Mesh_Record_Component.SCALAR
425+
DT = api.Datatype
426+
427+
ms["CHAR"][SCALAR].make_empty(DT.CHAR, 1)
428+
ms["UCHAR"][SCALAR].make_empty(DT.UCHAR, 2)
429+
ms["SHORT"][SCALAR].make_empty(DT.SHORT, 3)
430+
ms["INT"][SCALAR].make_empty(DT.INT, 4)
431+
ms["LONG"][SCALAR].make_empty(DT.LONG, 5)
432+
ms["LONGLONG"][SCALAR].make_empty(DT.LONGLONG, 6)
433+
ms["USHORT"][SCALAR].make_empty(DT.USHORT, 7)
434+
ms["UINT"][SCALAR].make_empty(DT.UINT, 8)
435+
ms["ULONG"][SCALAR].make_empty(DT.ULONG, 9)
436+
ms["ULONGLONG"][SCALAR].make_empty(DT.ULONGLONG, 10)
437+
ms["FLOAT"][SCALAR].make_empty(DT.FLOAT, 11)
438+
ms["DOUBLE"][SCALAR].make_empty(DT.DOUBLE, 12)
439+
ms["LONG_DOUBLE"][SCALAR].make_empty(DT.LONG_DOUBLE, 13)
440+
441+
if found_numpy:
442+
ms["int16"][SCALAR].make_empty(np.dtype("int16"), 14)
443+
ms["int32"][SCALAR].make_empty(np.dtype("int32"), 15)
444+
ms["int64"][SCALAR].make_empty(np.dtype("int64"), 16)
445+
ms["uint16"][SCALAR].make_empty(np.dtype("uint16"), 17)
446+
ms["uint32"][SCALAR].make_empty(np.dtype("uint32"), 18)
447+
ms["uint64"][SCALAR].make_empty(np.dtype("uint64"), 19)
448+
ms["single"][SCALAR].make_empty(np.dtype("single"), 20)
449+
ms["np_double"][SCALAR].make_empty(np.dtype("double"), 21)
450+
451+
# flush and close file
452+
del series
453+
454+
# read back
455+
series = api.Series(
456+
"unittest_py_empty_API." + file_ending,
457+
api.Access_Type.read_only
458+
)
459+
460+
ms = series.iterations[0].meshes
461+
462+
self.assertEqual(
463+
ms["CHAR"][SCALAR].shape,
464+
[0 for _ in range(1)]
465+
)
466+
self.assertEqual(
467+
ms["UCHAR"][SCALAR].shape,
468+
[0 for _ in range(2)]
469+
)
470+
self.assertEqual(
471+
ms["SHORT"][SCALAR].shape,
472+
[0 for _ in range(3)]
473+
)
474+
self.assertEqual(
475+
ms["INT"][SCALAR].shape,
476+
[0 for _ in range(4)]
477+
)
478+
self.assertEqual(
479+
ms["LONG"][SCALAR].shape,
480+
[0 for _ in range(5)]
481+
)
482+
self.assertEqual(
483+
ms["LONGLONG"][SCALAR].shape,
484+
[0 for _ in range(6)]
485+
)
486+
self.assertEqual(
487+
ms["USHORT"][SCALAR].shape,
488+
[0 for _ in range(7)]
489+
)
490+
self.assertEqual(
491+
ms["UINT"][SCALAR].shape,
492+
[0 for _ in range(8)]
493+
)
494+
self.assertEqual(
495+
ms["ULONG"][SCALAR].shape,
496+
[0 for _ in range(9)]
497+
)
498+
self.assertEqual(
499+
ms["ULONGLONG"][SCALAR].shape,
500+
[0 for _ in range(10)]
501+
)
502+
self.assertEqual(
503+
ms["FLOAT"][SCALAR].shape,
504+
[0 for _ in range(11)]
505+
)
506+
self.assertEqual(
507+
ms["DOUBLE"][SCALAR].shape,
508+
[0 for _ in range(12)]
509+
)
510+
self.assertEqual(
511+
ms["LONG_DOUBLE"][SCALAR].shape,
512+
[0 for _ in range(13)]
513+
)
514+
515+
if found_numpy:
516+
self.assertEqual(
517+
ms["int16"][SCALAR].shape,
518+
[0 for _ in range(14)]
519+
)
520+
self.assertEqual(
521+
ms["int32"][SCALAR].shape,
522+
[0 for _ in range(15)]
523+
)
524+
self.assertEqual(
525+
ms["int64"][SCALAR].shape,
526+
[0 for _ in range(16)]
527+
)
528+
self.assertEqual(
529+
ms["uint16"][SCALAR].shape,
530+
[0 for _ in range(17)]
531+
)
532+
self.assertEqual(
533+
ms["uint32"][SCALAR].shape,
534+
[0 for _ in range(18)]
535+
)
536+
self.assertEqual(
537+
ms["uint64"][SCALAR].shape,
538+
[0 for _ in range(19)]
539+
)
540+
self.assertEqual(
541+
ms["single"][SCALAR].shape,
542+
[0 for _ in range(20)]
543+
)
544+
self.assertEqual(
545+
ms["np_double"][SCALAR].shape,
546+
[0 for _ in range(21)]
547+
)
548+
549+
# test datatypes for fixed-sized types only
550+
if found_numpy:
551+
self.assertTrue(ms["int16"][SCALAR].dtype == np.dtype("int16"))
552+
self.assertTrue(ms["int32"][SCALAR].dtype == np.dtype("int32"))
553+
self.assertTrue(ms["int64"][SCALAR].dtype == np.dtype("int64"))
554+
self.assertTrue(ms["uint16"][SCALAR].dtype == np.dtype("uint16"))
555+
self.assertTrue(ms["uint32"][SCALAR].dtype == np.dtype("uint32"))
556+
self.assertTrue(ms["uint64"][SCALAR].dtype == np.dtype("uint64"))
557+
self.assertTrue(ms["single"][SCALAR].dtype == np.dtype("single"))
558+
self.assertTrue(
559+
ms["np_double"][SCALAR].dtype == np.dtype("double"))
560+
561+
def testEmptyRecords(self):
562+
backend_filesupport = {
563+
'json': 'json',
564+
'hdf5': 'h5',
565+
'adios1': 'bp'
566+
}
567+
for b in api.variants:
568+
if api.variants[b] is True and b in backend_filesupport:
569+
self.makeEmptyRoundTrip(backend_filesupport[b])
570+
416571
def testData(self):
417572
""" Test IO on data containing particles and meshes."""
418573

0 commit comments

Comments
 (0)