Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and test empty corner cases. #702

Merged
merged 2 commits into from
Mar 17, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions include/highfive/bits/H5Attribute_misc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ inline void Attribute::read(T& array) const {
throw DataSpaceException(ss.str());
}
auto dims = mem_space.getDimensions();

if (mem_space.getElementCount() == 0) {
auto effective_dims = details::squeezeDimensions(dims,
details::inspector<T>::recursive_ndim);

details::inspector<T>::prepare(array, effective_dims);
return;
}

auto r = details::data_converter::get_reader<T>(dims, array);
read(r.get_pointer(), buffer_info.data_type);
// re-arrange results
Expand Down Expand Up @@ -107,6 +116,11 @@ inline void Attribute::read(T* array, const DataType& dtype) const {
template <typename T>
inline void Attribute::write(const T& buffer) {
const DataSpace& mem_space = getMemSpace();

if (mem_space.getElementCount() == 0) {
return;
}

const details::BufferInfo<T> buffer_info(
getDataType(),
[this]() -> std::string { return this->getName(); },
Expand Down
5 changes: 3 additions & 2 deletions include/highfive/bits/H5Converter_misc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,11 @@ struct inspector<std::vector<T>> {
inspector<value_type>::is_trivially_copyable;

static std::vector<size_t> getDimensions(const type& val) {
std::vector<size_t> sizes{val.size()};
std::vector<size_t> sizes(recursive_ndim, 1ul);
sizes[0] = val.size();
if (!val.empty()) {
auto s = inspector<value_type>::getDimensions(val[0]);
sizes.insert(sizes.end(), s.begin(), s.end());
std::copy(s.begin(), s.end(), sizes.begin() + 1);
}
return sizes;
}
Expand Down
15 changes: 15 additions & 0 deletions include/highfive/bits/H5Slice_traits_misc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ template <typename T>
inline void SliceTraits<Derivate>::read(T& array, const DataTransferProps& xfer_props) const {
const auto& slice = static_cast<const Derivate&>(*this);
const DataSpace& mem_space = slice.getMemSpace();

const details::BufferInfo<T> buffer_info(
slice.getDataType(),
[slice]() -> std::string { return details::get_dataset(slice).getPath(); },
Expand All @@ -183,6 +184,15 @@ inline void SliceTraits<Derivate>::read(T& array, const DataTransferProps& xfer_
throw DataSpaceException(ss.str());
}
auto dims = mem_space.getDimensions();

if (mem_space.getElementCount() == 0) {
auto effective_dims = details::squeezeDimensions(dims,
details::inspector<T>::recursive_ndim);

details::inspector<T>::prepare(array, effective_dims);
return;
}

auto r = details::data_converter::get_reader<T>(dims, array);
read(r.get_pointer(), buffer_info.data_type, xfer_props);
// re-arrange results
Expand Down Expand Up @@ -231,6 +241,11 @@ template <typename T>
inline void SliceTraits<Derivate>::write(const T& buffer, const DataTransferProps& xfer_props) {
const auto& slice = static_cast<const Derivate&>(*this);
const DataSpace& mem_space = slice.getMemSpace();

if (mem_space.getElementCount() == 0) {
return;
}

const details::BufferInfo<T> buffer_info(
slice.getDataType(),
[slice]() -> std::string { return details::get_dataset(slice).getPath(); },
Expand Down
236 changes: 236 additions & 0 deletions tests/unit/tests_high_five_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1824,6 +1824,242 @@ TEST_CASE("ReadInBroadcastDims") {
}
}


template <int n_dim>
struct CreateEmptyVector;

template <>
struct CreateEmptyVector<1> {
using container_type = std::vector<int>;

static container_type create(const std::vector<size_t>& dims) {
return container_type(dims[0], 2);
}
};

template <int n_dim>
struct CreateEmptyVector {
using container_type = std::vector<typename CreateEmptyVector<n_dim - 1>::container_type>;

static container_type create(const std::vector<size_t>& dims) {
auto subdims = std::vector<size_t>(dims.begin() + 1, dims.end());
return container_type(dims[0], CreateEmptyVector<n_dim - 1>::create(subdims));
}
};

#ifdef H5_USE_BOOST
template <int n_dim>
struct CreateEmptyBoostMultiArray {
using container_type = boost::multi_array<int, static_cast<long unsigned>(n_dim)>;

static container_type create(const std::vector<size_t>& dims) {
auto container = container_type(dims);

auto raw_data = std::vector<int>(compute_total_size(dims));
container.assign(raw_data.begin(), raw_data.end());

return container;
}
};
#endif


#ifdef H5_USE_EIGEN
struct CreateEmptyEigenVector {
using container_type = Eigen::VectorXi;

static container_type create(const std::vector<size_t>& dims) {
return container_type::Constant(int(dims[0]), 2);
}
};

struct CreateEmptyEigenMatrix {
using container_type = Eigen::MatrixXi;

static container_type create(const std::vector<size_t>& dims) {
return container_type::Constant(int(dims[0]), int(dims[1]), 2);
}
};
#endif

template <class Container>
void check_empty_dimensions(const Container& container, const std::vector<size_t>& expected_dims) {
auto deduced_dims = details::inspector<Container>::getDimensions(container);

REQUIRE(expected_dims.size() == deduced_dims.size());

// The dims after hitting the first `0` are finicky. We allow those to be deduced as either `1`
// or what the original dims said. The `1` allows broadcasting, the "same as original" enables
// statically sized objects, which conceptually have dims, even if there's no object.
bool allow_one = false;
for (size_t i = 0; i < expected_dims.size(); ++i) {
REQUIRE(((expected_dims[i] == deduced_dims[i]) || (allow_one && (deduced_dims[i] == 1ul))));

if (expected_dims[i] == 0) {
allow_one = true;
}
}
}

template <class CreateContainer>
void check_empty_dimensions(const std::vector<size_t>& dims) {
auto input_data = CreateContainer::create(dims);
check_empty_dimensions(input_data, dims);
}

struct ReadWriteAttribute {
template <class Container>
static void create(HighFive::File& file, const std::string& name, const Container& container) {
file.createAttribute(name, container);
}

static HighFive::Attribute get(HighFive::File& file, const std::string& name) {
return file.getAttribute(name);
}
};

struct ReadWriteDataSet {
template <class Container>
static void create(HighFive::File& file, const std::string& name, const Container& container) {
file.createDataSet(name, container);
}

static HighFive::DataSet get(HighFive::File& file, const std::string& name) {
return file.getDataSet(name);
}
};

template <class ReadWriteInterface, class CreateContainer>
void check_empty_read_write_cycle(const std::vector<size_t>& dims) {
using container_type = typename CreateContainer::container_type;

const std::string FILE_NAME("h5_empty_attr.h5");
const std::string DATASET_NAME("dset");
File file(FILE_NAME, File::Truncate);

auto input_data = CreateContainer::create(dims);
ReadWriteInterface::create(file, DATASET_NAME, input_data);

SECTION("read; one-dimensional vector (empty)") {
auto output_data = CreateEmptyVector<1>::create({0ul});

ReadWriteInterface::get(file, DATASET_NAME).read(output_data);
check_empty_dimensions(output_data, {0ul});
}

SECTION("read; pre-allocated (empty)") {
auto output_data = CreateContainer::create(dims);
ReadWriteInterface::get(file, DATASET_NAME).read(output_data);

check_empty_dimensions(output_data, dims);
}

SECTION("read; pre-allocated (oversized)") {
auto oversize_dims = std::vector<size_t>(dims.size(), 2ul);
auto output_data = CreateContainer::create(oversize_dims);
ReadWriteInterface::get(file, DATASET_NAME).read(output_data);

check_empty_dimensions(output_data, dims);
}

SECTION("read; auto-allocated") {
auto output_data =
ReadWriteInterface::get(file, DATASET_NAME).template read<container_type>();
check_empty_dimensions(output_data, dims);
}
}

template <class CreateContainer>
void check_empty_dataset(const std::vector<size_t>& dims) {
check_empty_read_write_cycle<ReadWriteDataSet, CreateContainer>(dims);
}

template <class CreateContainer>
void check_empty_attribute(const std::vector<size_t>& dims) {
check_empty_read_write_cycle<ReadWriteAttribute, CreateContainer>(dims);
}

template <class CreateContainer>
void check_empty_everything(const std::vector<size_t>& dims) {
SECTION("Empty dimensions") {
check_empty_dimensions<CreateContainer>(dims);
}

SECTION("Empty datasets") {
check_empty_dataset<CreateContainer>(dims);
}

SECTION("Empty attribute") {
check_empty_attribute<CreateContainer>(dims);
}
}

#ifdef H5_USE_EIGEN
template <int ndim>
void check_empty_eigen(const std::vector<size_t>&) {}

template <>
void check_empty_eigen<1>(const std::vector<size_t>& dims) {
SECTION("Eigen::Vector") {
check_empty_everything<CreateEmptyEigenVector>({dims[0], 1ul});
}
}

template <>
void check_empty_eigen<2>(const std::vector<size_t>& dims) {
SECTION("Eigen::Matrix") {
check_empty_everything<CreateEmptyEigenMatrix>(dims);
}
}
#endif

template <int ndim>
void check_empty(const std::vector<size_t>& dims) {
REQUIRE(dims.size() == ndim);

SECTION("std::vector") {
check_empty_everything<CreateEmptyVector<ndim>>(dims);
}

#ifdef H5_USE_BOOST
SECTION("boost::multi_array") {
check_empty_everything<CreateEmptyBoostMultiArray<ndim>>(dims);
}
#endif

#ifdef H5_USE_EIGEN
check_empty_eigen<ndim>(dims);
#endif
}

TEST_CASE("Empty arrays") {
SECTION("one-dimensional") {
check_empty<1>({0ul});
}

SECTION("two-dimensional") {
std::vector<std::vector<size_t>> testcases{{0ul, 1ul}, {1ul, 0ul}};

for (const auto& dims: testcases) {
SECTION(details::format_vector(dims)) {
check_empty<2>(dims);
}
}
}

SECTION("three-dimensional") {
std::vector<std::vector<size_t>> testcases{{0ul, 1ul, 1ul},
{1ul, 1ul, 0ul},
{1ul, 0ul, 1ul}};

for (const auto& dims: testcases) {
SECTION(details::format_vector(dims)) {
check_empty<3>(dims);
}
}
}
}

TEST_CASE("HighFiveRecursiveGroups") {
const std::string FILE_NAME("h5_ds_exist.h5");
const std::string GROUP_1("group1"), GROUP_2("group2");
Expand Down