Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions include/openPMD/RecordComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ class RecordComponent : public BaseRecordComponent
template< typename T >
RecordComponent& makeConstant(T);

/**
* Create a dataset with zero extent in each dimension.
* Implemented by creating a constant record component with

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potentially add an empty comment line to separate title and brief description in doxygen :)

* a dummy value (default constructor of the respective datatype).
* @param dimensions The number of dimensions. Must be greater than
* zero.
* @return A reference to this RecordComponent.
*/
template< typename T >
RecordComponent& makeEmpty( uint8_t dimensions );

This comment was marked as resolved.


/** Load and allocate a chunk of data
*
* Set offset to {0u} and extent to {-1u} for full selection.
Expand Down Expand Up @@ -155,10 +166,19 @@ class RecordComponent : public BaseRecordComponent

std::shared_ptr< std::queue< IOTask > > m_chunks;
std::shared_ptr< Attribute > m_constantValue;
std::shared_ptr< bool > m_isEmpty = std::make_shared< bool >( false );

private:
void flush(std::string const&);
virtual void read();

/**
* Internal method to be called by all methods that create an empty dataset.
*
* @param The dataset description. Must have nonzero dimensions.
* @return Reference to this RecordComponent instance.
*/
RecordComponent& makeEmpty( Dataset );

This comment was marked as resolved.

}; // RecordComponent


Expand All @@ -174,6 +194,15 @@ RecordComponent::makeConstant(T value)
return *this;
}

template< typename T >
inline RecordComponent&
RecordComponent::makeEmpty( uint8_t dimensions )
{
return makeEmpty( Dataset(
determineDatatype< T >(),
Extent( dimensions, 0 ) ) );
}

template< typename T >
inline std::shared_ptr< T >
RecordComponent::loadChunk(Offset o, Extent e, double targetUnitSI)
Expand Down Expand Up @@ -283,9 +312,11 @@ inline void
RecordComponent::storeChunk(std::shared_ptr<T> data, Offset o, Extent e)
{
if( *m_isConstant )
throw std::runtime_error("Chunks can not be written for a constant RecordComponent.");
throw std::runtime_error("Chunks cannot be written for a constant RecordComponent.");
if( *m_isEmpty )
throw std::runtime_error("Chunks cannot be written for an empty RecordComponent.");
if( !data )
throw std::runtime_error("Unallocated pointer passed during chunk store.");
throw std::runtime_error("Unallocated pointer passed during chunk store.");
Datatype dtype = determineDatatype(data);
if( dtype != getDatatype() )
{
Expand Down
30 changes: 30 additions & 0 deletions include/openPMD/backend/BaseRecordComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,34 @@ class BaseRecordComponent : public Attributable
std::shared_ptr< Dataset > m_dataset;
std::shared_ptr< bool > m_isConstant;
}; // BaseRecordComponent

namespace detail

This comment was marked as resolved.

{
/**
* Functor template to be used in combination with <switchType>"()"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also keep a one-line title and a separate brief description here.

* to set a default value for constant record components via the
* respective type's default constructor.
* Used to implement empty datasets in subclasses of BaseRecordComponent
* (currently RecordComponent).
* @param rc
*/
template< typename RecordComponent_T >
struct DefaultValue

This comment was marked as resolved.

{
template< typename T >
void
operator()( RecordComponent_T & rc )
{
rc.makeConstant( T() );
}

template< unsigned n, typename... Args >
void
operator()( Args &&... )
{
throw std::runtime_error(
"makeEmpty: Datatype not supported by openPMD." );
}
};
} // namespace detail
} // namespace openPMD
36 changes: 30 additions & 6 deletions src/RecordComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,19 @@ RecordComponent::setUnitSI(double usi)
return *this;
}

RecordComponent&
RecordComponent::resetDataset(Dataset d)
RecordComponent &
RecordComponent::resetDataset( Dataset d )
{
if( written )
throw std::runtime_error("A Records Dataset can not (yet) be changed after it has been written.");
throw std::runtime_error( "A record's Dataset cannot (yet) be changed "
"after it has been written." );
if( d.extent.empty() )
throw std::runtime_error("Dataset extent must be at least 1D.");
if( std::any_of(d.extent.begin(), d.extent.end(),
[](Extent::value_type const& i) { return i == 0u; }) )
throw std::runtime_error("Dataset extent must not be zero in any dimension.");
if( std::any_of(
d.extent.begin(),
d.extent.end(),
[]( Extent::value_type const & i ) { return i == 0u; } ) )
return makeEmpty( d );

*m_dataset = d;
dirty = true;
Expand All @@ -74,6 +77,27 @@ RecordComponent::getExtent() const
return m_dataset->extent;
}

RecordComponent&
RecordComponent::makeEmpty( Dataset d )
{
if( written )
throw std::runtime_error(
"A RecordComponent cannot (yet) be made"
" empty after it has been written.");
if( d.extent.size() == 0 )
throw std::runtime_error("Dataset extent must be at least 1D.");

*m_isEmpty = true;
*m_dataset = d;
dirty = true;
static detail::DefaultValue< RecordComponent > dv;
switchType(
d.dtype,
dv,
*this );
return *this;
}

void
RecordComponent::flush(std::string const& name)
{
Expand Down
11 changes: 3 additions & 8 deletions test/CoreTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -677,15 +677,10 @@ TEST_CASE( "zero_extent_component", "[core]" )
Series o = Series("./new_openpmd_output", AccessType::CREATE);

auto E_x = o.iterations[1].meshes["E"]["x"];
E_x.setComment("Datasets with zero extent in any dimension are not allowed.");
REQUIRE_THROWS_WITH(E_x.resetDataset(Dataset(Datatype::LONG, {0})),
Catch::Equals("Dataset extent must not be zero in any dimension."));
REQUIRE_THROWS_WITH(E_x.resetDataset(Dataset(Datatype::LONG, {1, 0})),
Catch::Equals("Dataset extent must not be zero in any dimension."));
REQUIRE_THROWS_WITH(E_x.resetDataset(Dataset(Datatype::LONG, {0, 1})),
Catch::Equals("Dataset extent must not be zero in any dimension."));
E_x.setComment("Datasets must contain dimensions.");
REQUIRE_THROWS_WITH(E_x.resetDataset(Dataset(Datatype::LONG, {})),
Catch::Equals("Dataset extent must be at least 1D."));
REQUIRE_THROWS_WITH(E_x.makeEmpty<int>(0),
Catch::Equals("Dataset extent must be at least 1D."));
E_x.resetDataset(Dataset(Datatype::DOUBLE, {1}));
}
}
68 changes: 66 additions & 2 deletions test/SerialIOTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,70 @@ std::vector<std::tuple<std::string, bool>> getBackends() {

auto const backends = getBackends();

inline
void
empty_dataset_test( std::string file_ending )
{
{
Series series(
"../samples/empty_datasets." + file_ending, AccessType::CREATE );

auto makeEmpty_dim_7_int =
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_int" ];
auto makeEmpty_dim_7_long =
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_bool" ];
auto makeEmpty_resetDataset_dim3 =
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_resetDataset_dim3" ];
auto makeEmpty_resetDataset_dim3_notallzero =
series.iterations[ 1 ]
.meshes[ "rho" ][ "makeEmpty_resetDataset_dim3_notallzero" ];
makeEmpty_dim_7_int.makeEmpty< int >( 7 );
makeEmpty_dim_7_long.makeEmpty< long >( 7 );
makeEmpty_resetDataset_dim3.resetDataset(
Dataset( Datatype::LONG, Extent( 3, 0 ) ) );
makeEmpty_resetDataset_dim3_notallzero.resetDataset(
Dataset( Datatype::LONG_DOUBLE, Extent{ 1, 2, 0 } ) );
series.flush();

}
{
Series series(
"../samples/empty_datasets." + file_ending, AccessType::READ_ONLY );
auto makeEmpty_dim_7_int =
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_int" ];
auto makeEmpty_dim_7_long =
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_dim_7_bool" ];
auto makeEmpty_resetDataset_dim3 =
series.iterations[ 1 ].meshes[ "rho" ][ "makeEmpty_resetDataset_dim3" ];
auto makeEmpty_resetDataset_dim3_notallzero =
series.iterations[ 1 ]
.meshes[ "rho" ][ "makeEmpty_resetDataset_dim3_notallzero" ];
REQUIRE(makeEmpty_dim_7_int.getDimensionality() == 7);
REQUIRE(makeEmpty_dim_7_int.getExtent() == Extent(7, 0));
REQUIRE(isSame(makeEmpty_dim_7_int.getDatatype(), determineDatatype< int >()));

REQUIRE(makeEmpty_dim_7_long.getDimensionality() == 7);
REQUIRE(makeEmpty_dim_7_long.getExtent() == Extent(7, 0));
REQUIRE(isSame(makeEmpty_dim_7_long.getDatatype(), determineDatatype< long >()));

REQUIRE(makeEmpty_resetDataset_dim3.getDimensionality() == 3);
REQUIRE(makeEmpty_resetDataset_dim3.getExtent() == Extent(3, 0));
REQUIRE(isSame(makeEmpty_resetDataset_dim3.getDatatype(), Datatype::LONG));

REQUIRE(makeEmpty_resetDataset_dim3_notallzero.getDimensionality() == 3);
REQUIRE(makeEmpty_resetDataset_dim3_notallzero.getExtent() == Extent{1,2,0});
REQUIRE(isSame(makeEmpty_resetDataset_dim3_notallzero.getDatatype(), Datatype::LONG_DOUBLE));
}
}

TEST_CASE( "empty_dataset_test", "[serial]" )
{
for (auto const & t: backends)
{
empty_dataset_test(std::get<0>(t));
}
}

inline
void constant_scalar(std::string file_ending)
{
Expand Down Expand Up @@ -187,9 +251,9 @@ TEST_CASE( "flush_without_position_positionOffset", "[serial]" )
[]( float * ptr ) { delete[] ptr; } ),
{ 0, 0 },
{ 2, 2 } );

s.flush();

for( auto const & key : { "position", "positionOffset" } )
{
for( auto const & dim : { "x", "y", "z" } )
Expand Down