Program Listing for File h5write.hpp

↰ Return to documentation for file (include/dx2/h5/h5write.hpp)

#pragma once

#include "dx2/h5/h5dispatch.hpp"
#include "dx2/h5/h5utils.hpp"
#include <array>
#include <cstdlib>
#include <hdf5.h>
#include <string>
#include <vector>

#pragma region Raw writer
h5utils::H5Group traverse_or_create_groups(hid_t parent,
                                           const std::string &path);

template <typename T>
void write_raw_data_to_h5_file(std::string_view filename,
                               std::string_view dataset_path, const T *data_ptr,
                               const std::vector<hsize_t> &shape) {
  // Convert to std::string when needed for C-style API
  std::string fname(filename);
  std::string dset_path(dataset_path);

  // Suppress errors when opening non-existent files, groups, datasets..
  H5ErrorSilencer silencer;

  // Open or create the file
  h5utils::H5File file(H5Fopen(fname.c_str(), H5F_ACC_RDWR, H5P_DEFAULT));
  if (!file) {
    file = h5utils::H5File(
        H5Fcreate(fname.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT));
    if (!file) {
      throw std::runtime_error("Failed to create or open file: " + fname);
    }
  }
  // Split group and dataset name
  size_t slash_pos = dset_path.find_last_of('/');
  if (slash_pos == std::string::npos) {
    throw std::runtime_error("Invalid dataset path: " + dset_path);
  }
  std::string group_path = dset_path.substr(0, slash_pos);
  std::string dataset_name = dset_path.substr(slash_pos + 1);

  h5utils::H5Group group = traverse_or_create_groups(file, group_path);
  if (!group) {
    throw std::runtime_error("Failed to create or open group: " + group_path);
  }

  // Create dataspace and determine type
  h5utils::H5Space dataspace(
      H5Screate_simple(shape.size(), shape.data(), nullptr));
  h5utils::H5Type h5_type = h5dispatch::make_h5_type<T>();

  // Create or open dataset
  h5utils::H5Dataset dset(H5Dcreate2(group, dataset_name.c_str(), h5_type,
                                     dataspace, H5P_DEFAULT, H5P_DEFAULT,
                                     H5P_DEFAULT));
  if (!dset) {
    dset =
        h5utils::H5Dataset(H5Dopen2(group, dataset_name.c_str(), H5P_DEFAULT));
    if (!dset) {
      throw std::runtime_error("Failed to create or open dataset: " +
                               dataset_name);
    }
  }

  herr_t status =
      H5Dwrite(dset, h5_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, data_ptr);
  if (status < 0) {
    throw std::runtime_error("Failed to write dataset: " + dset_path);
  }
}

template <typename T>
void write_raw_data_to_h5_group(h5utils::H5Group &group,
                                const std::string &dataset_name,
                                const T *data_ptr,
                                const std::vector<hsize_t> &shape) {
  // Suppress errors when opening non-existent files, groups, datasets..
  H5ErrorSilencer silencer;

  // Create dataspace and determine type
  h5utils::H5Space dataspace(
      H5Screate_simple(shape.size(), shape.data(), nullptr));
  h5utils::H5Type h5_type = h5dispatch::make_h5_type<T>();

  // Create or open dataset
  h5utils::H5Dataset dset(H5Dcreate2(group, dataset_name.c_str(), h5_type,
                                     dataspace, H5P_DEFAULT, H5P_DEFAULT,
                                     H5P_DEFAULT));
  if (!dset) {
    dset =
        h5utils::H5Dataset(H5Dopen2(group, dataset_name.c_str(), H5P_DEFAULT));
    if (!dset) {
      throw std::runtime_error("Failed to create or open dataset: " +
                               dataset_name);
    }
  }

  herr_t status =
      H5Dwrite(dset, h5_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, data_ptr);
  if (status < 0) {
    throw std::runtime_error("Failed to write dataset: " + dataset_name);
  }
}
#pragma endregion
#pragma region High-level writer
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * The high-level writer is useful for writing nested containers. While
 * not utilised within the DX2 backend itself, it is a useful utility
 * for writing data to HDF5 files, thus it is included here.
 */

template <typename Container>
std::vector<hsize_t> deduce_shape(const Container &container) {
  if (container.empty()) {
    return {0};
  }
  if constexpr (std::is_arithmetic_v<typename Container::value_type>) {
    // Base case: container holds arithmetic types (e.g., double, int)
    return {container.size()};
  } else {
    // Recursive case: container holds other containers

    // Check that all inner containers have the same size
    size_t inner_size = container.begin()->size();
    for (const auto &sub_container : container) {
      if (sub_container.size() != inner_size) {
        throw std::runtime_error("Cannot deduce shape: inner containers have "
                                 "different sizes.");
      }
    }

    auto sub_shape = deduce_shape(*container.begin());
    sub_shape.insert(sub_shape.begin(), container.size());
    return sub_shape;
  }
}

template <typename Container> auto flatten(const Container &container) {
  // Determine the type of the elements in the container
  using ValueType = typename Container::value_type;

  // Base case: If the container holds arithmetic types (e.g., int, double),
  // it is already 1D, so we return a copy of the container as a std::vector.
  if constexpr (std::is_arithmetic_v<ValueType>) {
    return std::vector<ValueType>(container.begin(), container.end());
  } else {
    // Recursive case: The container holds nested containers, so we need to
    // flatten them.

    // Determine the type of elements in the flattened result.
    // This is deduced by recursively calling flatten on the first
    // sub-container.
    using InnerType =
        typename decltype(flatten(*container.begin()))::value_type;

    // Create a vector to store the flattened data
    std::vector<InnerType> flat_data;

    // Iterate over the outer container
    for (const auto &sub_container : container) {
      // Recursively flatten each sub-container
      auto sub_flat = flatten(sub_container);

      // Append the flattened sub-container to the result
      flat_data.insert(flat_data.end(), sub_flat.begin(), sub_flat.end());
    }

    // Return the fully flattened data
    return flat_data;
  }
}

template <typename Container>
void write_data_to_h5_file(std::string_view filename,
                           std::string_view dataset_path,
                           const Container &data) {
  std::string fname(filename);
  std::string dset_path(dataset_path);

  // Suppress errors when opening non-existent files, groups, datasets..
  H5ErrorSilencer silencer;

  // Open or create the HDF5 file
  h5utils::H5File file(H5Fopen(fname.c_str(), H5F_ACC_RDWR, H5P_DEFAULT));
  if (!file) {
    file = h5utils::H5File(
        H5Fcreate(fname.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT));
    if (!file) {
      throw std::runtime_error("Failed to create or open file: " + fname);
    }
  }

  // Separate the dataset path into group path and dataset name
  size_t last_slash_pos = dset_path.find_last_of('/');
  if (last_slash_pos == std::string::npos) {
    throw std::runtime_error("Error: Invalid dataset path, no '/' found: " +
                             dset_path);
  }

  std::string group_path = dset_path.substr(0, last_slash_pos);
  std::string dataset_name = dset_path.substr(last_slash_pos + 1);

  // Traverse or create the groups leading to the dataset
  h5utils::H5Group group = traverse_or_create_groups(file, group_path);
  if (!group) {
    throw std::runtime_error("Failed to create or open group: " + group_path);
  }

  // Deduce the shape of the data
  std::vector<hsize_t> shape = deduce_shape(data);

  // Flatten the data into a 1D vector
  auto flat_data = flatten(data);

  // Check if dataset exists
  h5utils::H5Dataset dataset(H5Dopen(group, dataset_name.c_str(), H5P_DEFAULT));
  if (!dataset) {
    // Dataset does not exist, create it
    h5utils::H5Space dataspace(
        H5Screate_simple(shape.size(), shape.data(), nullptr));
    dataset = h5utils::H5Dataset(
        H5Dcreate(group, dataset_name.c_str(), H5T_NATIVE_DOUBLE, dataspace,
                  H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT));
    if (!dataset) {
      throw std::runtime_error("Error: Unable to create dataset: " +
                               dataset_name);
    }
  } else {
    // Dataset exists, check if the shape matches
    h5utils::H5Space existing_space(H5Dget_space(dataset));
    int ndims = H5Sget_simple_extent_ndims(existing_space);
    std::vector<hsize_t> existing_dims(ndims);
    H5Sget_simple_extent_dims(existing_space, existing_dims.data(), nullptr);

    if (existing_dims != shape) {
      throw std::runtime_error(
          "Error: Dataset shape mismatch. Cannot overwrite dataset: " +
          dataset_name);
    }
  }

  // Write the data to the dataset
  herr_t status = H5Dwrite(dataset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
                           H5P_DEFAULT, flat_data.data());
  if (status < 0) {
    throw std::runtime_error("Error: Unable to write data to dataset: " +
                             dataset_name);
  }
}
#pragma endregion
#pragma region Attribute writer
void write_experiment_metadata(hid_t group_id,
                               const std::vector<uint64_t> &experiment_ids,
                               const std::vector<std::string> &identifiers);
#pragma endregion