From 35908346b20b524d4d2d9f8f6220800c27595413 Mon Sep 17 00:00:00 2001 From: Stephane Del Pino <stephane.delpino44@gmail.com> Date: Mon, 10 Jun 2024 22:21:56 +0200 Subject: [PATCH] git subrepo pull packages/HighFive subrepo: subdir: "packages/HighFive" merged: "b74fabbea" upstream: origin: "git@github.com:BlueBrain/HighFive.git" branch: "master" commit: "b74fabbea" git-subrepo: version: "0.4.6" origin: "git@github.com:ingydotnet/git-subrepo.git" commit: "73a0129" --- CMakeLists.txt | 1 - packages/HighFive/.github/workflows/ci.yml | 25 +- packages/HighFive/.gitrepo | 4 +- packages/HighFive/.travis.yml | 138 --- packages/HighFive/CMakeLists.txt | 17 +- packages/HighFive/README.md | 2 +- .../cmake/HighFiveOptionalDependencies.cmake | 8 + .../HighFive/cmake/HighFiveWarnings.cmake | 44 +- packages/HighFive/doc/developer_guide.md | 7 +- .../doxygen-awesome-css/doxygen-awesome.css | 22 +- packages/HighFive/doc/migration_guide.md | 114 ++- .../HighFive/include/highfive/H5Attribute.hpp | 35 +- packages/HighFive/include/highfive/H5File.hpp | 2 +- .../HighFive/include/highfive/H5Object.hpp | 26 +- .../include/highfive/H5PropertyList.hpp | 6 - .../HighFive/include/highfive/H5Selection.hpp | 8 +- .../highfive/bits/H5Attribute_misc.hpp | 42 +- .../highfive/bits/H5Converter_misc.hpp | 6 +- .../include/highfive/bits/H5DataType_misc.hpp | 6 +- .../highfive/bits/H5Dataspace_misc.hpp | 7 +- .../include/highfive/bits/H5File_misc.hpp | 2 +- .../highfive/bits/H5Inspector_decl.hpp | 10 +- .../highfive/bits/H5Inspector_misc.hpp | 189 ++-- .../include/highfive/bits/H5Node_traits.hpp | 3 - .../highfive/bits/H5Node_traits_misc.hpp | 16 +- .../include/highfive/bits/H5Object_misc.hpp | 6 - .../highfive/bits/H5PropertyList_misc.hpp | 9 - .../highfive/bits/H5ReadWrite_misc.hpp | 27 +- .../highfive/bits/H5Selection_misc.hpp | 8 +- .../include/highfive/bits/H5Slice_traits.hpp | 50 +- .../highfive/bits/H5Slice_traits_misc.hpp | 45 +- .../bits/assert_compatible_spaces.hpp | 29 + .../highfive/bits/compute_total_size.hpp | 14 + .../highfive/bits/convert_size_vector.hpp | 31 + .../include/highfive/bits/h5o_wrapper.hpp | 9 + .../include/highfive/bits/squeeze.hpp | 54 ++ packages/HighFive/include/highfive/boost.hpp | 45 +- packages/HighFive/include/highfive/eigen.hpp | 12 +- .../include/highfive/experimental/opencv.hpp | 149 ++++ .../highfive/h5easy_bits/H5Easy_xtensor.hpp | 10 + packages/HighFive/include/highfive/span.hpp | 102 +++ .../HighFive/include/highfive/xtensor.hpp | 212 +++++ packages/HighFive/src/examples/CMakeLists.txt | 12 +- .../src/examples/broadcasting_arrays.cpp | 50 ++ .../src/examples/read_write_std_span.cpp | 56 ++ packages/HighFive/tests/unit/CMakeLists.txt | 59 +- .../HighFive/tests/unit/data_generator.hpp | 159 +++- .../HighFive/tests/unit/supported_types.hpp | 39 + .../HighFive/tests/unit/test_empty_arrays.cpp | 248 ++++++ packages/HighFive/tests/unit/test_legacy.cpp | 36 + packages/HighFive/tests/unit/test_opencv.cpp | 59 ++ packages/HighFive/tests/unit/test_string.cpp | 351 ++++++++ packages/HighFive/tests/unit/test_xtensor.cpp | 142 +++ .../HighFive/tests/unit/tests_high_five.hpp | 1 + .../tests/unit/tests_high_five_base.cpp | 820 +++--------------- .../tests/unit/tests_high_five_easy.cpp | 38 + .../tests/unit/tests_high_five_multi_dims.cpp | 9 + 57 files changed, 2442 insertions(+), 1189 deletions(-) delete mode 100644 packages/HighFive/.travis.yml create mode 100644 packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp create mode 100644 packages/HighFive/include/highfive/bits/compute_total_size.hpp create mode 100644 packages/HighFive/include/highfive/bits/convert_size_vector.hpp create mode 100644 packages/HighFive/include/highfive/bits/squeeze.hpp create mode 100644 packages/HighFive/include/highfive/experimental/opencv.hpp create mode 100644 packages/HighFive/include/highfive/span.hpp create mode 100644 packages/HighFive/include/highfive/xtensor.hpp create mode 100644 packages/HighFive/src/examples/broadcasting_arrays.cpp create mode 100644 packages/HighFive/src/examples/read_write_std_span.cpp create mode 100644 packages/HighFive/tests/unit/test_empty_arrays.cpp create mode 100644 packages/HighFive/tests/unit/test_opencv.cpp create mode 100644 packages/HighFive/tests/unit/test_string.cpp create mode 100644 packages/HighFive/tests/unit/test_xtensor.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 94da14ad5..51f020ba5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,7 +234,6 @@ if (PUGS_ENABLE_HDF5 MATCHES "^(AUTO|ON)$") set(HIGHFIVE_UNIT_TESTS OFF) # no unit tests set(HIGHFIVE_UNIT_TESTS OFF) # no unit tests set(HIGHFIVE_EXAMPLES OFF) # no examples - set(HIGHFIVE_PARALLEL_HDF5 ON) # activate parallel HDF5 add_subdirectory(${PUGS_SOURCE_DIR}/packages/HighFive/) set(HIGHFIVE_TARGET HighFive) endif() diff --git a/packages/HighFive/.github/workflows/ci.yml b/packages/HighFive/.github/workflows/ci.yml index 49f415e82..66069123d 100644 --- a/packages/HighFive/.github/workflows/ci.yml +++ b/packages/HighFive/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: flags: '-DCMAKE_CXX_STANDARD=17 -DHIGHFIVE_TEST_BOOST:Bool=ON' - config: os: ubuntu-22.04 - flags: '-DHIGHFIVE_TEST_BOOST=Off -DCMAKE_CXX_STANDARD=20' + flags: '-DHIGHFIVE_TEST_BOOST=Off -DCMAKE_CXX_STANDARD=20 -DHIGHFIVE_HAS_CONCEPTS=On' steps: - uses: actions/checkout@v3 @@ -76,7 +76,7 @@ jobs: - name: Build run: | - CMAKE_OPTIONS=(-DHIGHFIVE_PARALLEL_HDF5:BOOL=ON ${{ matrix.config.flags }}) + CMAKE_OPTIONS=(-DHDF5_PREFER_PARALLEL:BOOL=ON ${{ matrix.config.flags }}) source $GITHUB_WORKSPACE/.github/build.sh - name: Test @@ -293,10 +293,13 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - os: [ "macOS-12" ] + os: [ "macOS-14" ] cxxstd: ["14", "17", "20"] include: + - os: "macOS-12" + cxxstd: "14" + - os: "macOS-13" cxxstd: "20" @@ -316,8 +319,6 @@ jobs: -DHIGHFIVE_TEST_EIGEN:BOOL=ON -DHIGHFIVE_TEST_XTENSOR:BOOL=ON -DHIGHFIVE_BUILD_DOCS:BOOL=FALSE - -DHIGHFIVE_TEST_SINGLE_INCLUDES=ON - -DCMAKE_CXX_FLAGS="-coverage -O0" -DCMAKE_CXX_STANDARD=${{matrix.cxxstd}} ) source $GITHUB_WORKSPACE/.github/build.sh @@ -343,19 +344,9 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - os: [ "windows-2022"] - vs-toolset: [ "v141", "v143" ] + os: [ "windows-2019", "windows-2022"] cxxstd: ["14", "17", "20"] - include: - - os: "windows-2019" - vs-toolset: "v142" - cxxstd: "14" - - - os: "windows-2019" - vs-toolset: "v142" - cxxstd: "17" - steps: - uses: actions/checkout@v3 with: @@ -370,13 +361,11 @@ jobs: shell: bash -l {0} run: | CMAKE_OPTIONS=( - -T ${{matrix.vs-toolset}} -DCMAKE_CXX_STANDARD=${{matrix.cxxstd}} -DHIGHFIVE_UNIT_TESTS=ON -DHIGHFIVE_TEST_BOOST:BOOL=ON -DHIGHFIVE_TEST_EIGEN:BOOL=ON -DHIGHFIVE_TEST_XTENSOR:BOOL=ON - -DHIGHFIVE_TEST_SINGLE_INCLUDES=ON ) source $GITHUB_WORKSPACE/.github/build.sh diff --git a/packages/HighFive/.gitrepo b/packages/HighFive/.gitrepo index 308a575dd..ba3061909 100644 --- a/packages/HighFive/.gitrepo +++ b/packages/HighFive/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = git@github.com:BlueBrain/HighFive.git branch = master - commit = 5e0204e272e4f71e3dd768287e400758f785527d - parent = 760bc7af37b9a29960a20602cfc91e35e22e24a6 + commit = b74fabbea7bc4422a6891b2f5a27305b8dd8cd1b + parent = d64d966d026b0bc07529a0328b8607036ecc24d6 method = merge cmdver = 0.4.6 diff --git a/packages/HighFive/.travis.yml b/packages/HighFive/.travis.yml deleted file mode 100644 index bc5d34081..000000000 --- a/packages/HighFive/.travis.yml +++ /dev/null @@ -1,138 +0,0 @@ -# Adapted from various sources, including: -# - Louis Dionne's Hana: https://github.com/ldionne/hana -# - Paul Fultz II's FIT: https://github.com/pfultz2/Fit -# - Eric Niebler's range-v3: https://github.com/ericniebler/range-v3 -# - Gabi Melman spdlog: https://github.com/gabime/spdlog - -sudo: required -language: cpp - -addons: &gcc7 - apt: - packages: - - g++-7 - - libboost-all-dev - - libhdf5-openmpi-dev - - libeigen3-dev - - ninja-build - sources: - - ubuntu-toolchain-r-test - -matrix: - include: - # Older linux (trusty) with default gcc - # Install serial hdf5 + build serial - - os: linux - dist: trusty - env: - - HIGHFIVE_USE_XTENSOR=False - - HIGHFIVE_USE_OPENCV=False - - HIGHFIVE_PARALLEL_HDF5=False - - IS_BASE_ENVIRON=1 - addons: - apt: - packages: - - libboost-all-dev - - libeigen3-dev - - libhdf5-serial-dev - - ninja-build - - # Linux gcc-7 - # Install parallel hdf5 + build parallel - - os: linux - dist: xenial - env: - - GCC_VERSION=7 - - HIGHFIVE_USE_XTENSOR=True - - HIGHFIVE_USE_OPENCV=False - - HIGHFIVE_PARALLEL_HDF5=True - addons: *gcc7 - - # Mac OSX XCode 10 - - os: osx - osx_image: xcode10.3 - env: - - HIGHFIVE_USE_XTENSOR=True - - HIGHFIVE_USE_OPENCV=True - - HIGHFIVE_PARALLEL_HDF5=False - - # Windows - - os: windows - env: - - HIGHFIVE_USE_XTENSOR=True - - HIGHFIVE_USE_OPENCV=True - - HIGHFIVE_PARALLEL_HDF5=False - -env: - global: - - MINCONDA_VERSION="latest" - - MINCONDA_LINUX="Linux-x86_64" - - MINCONDA_OSX="MacOSX-x86_64" - -install: - - export HOMEBREW_NO_AUTO_UPDATE=1 # for reproducibility, dont autoupdate - - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - MINCONDA_OS=$MINCONDA_LINUX; - elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - if [ "$BREW_USE_LATEST" ]; then - brew update; - brew install hdf5; brew upgrade hdf5; - fi; - brew install boost hdf5 eigen ninja; - MINCONDA_OS=$MINCONDA_OSX; - fi - - - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then - export CMAKE_GENERATOR="Visual Studio 15 2017 Win64" ; - export TESTS_TARGET="RUN_TESTS"; - choco install --yes miniconda3 ; - source C:/Tools/miniconda3/Scripts/activate ; - else - export CMAKE_GENERATOR="Ninja" ; - export TESTS_TARGET="test"; - wget "http://repo.continuum.io/miniconda/Miniconda3-$MINCONDA_VERSION-$MINCONDA_OS.sh" -O miniconda.sh; - bash miniconda.sh -b -p $HOME/miniconda ; - source $HOME/miniconda/bin/activate; - hash -r ; - fi - - conda config --set always_yes yes --set changeps1 no - - conda update -q conda - - conda install -c conda-forge mamba - - if [[ "$HIGHFIVE_USE_XTENSOR" == "True" ]]; then - mamba install -c conda-forge xtl xsimd xtensor; - fi - - if [[ "$HIGHFIVE_USE_OPENCV" == "True" ]]; then - mamba install -c conda-forge libopencv opencv; - fi - - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then - mamba install -c conda-forge boost-cpp hdf5 eigen; - fi - -before_script: - - if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi - - if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CXX="clang++" CC="clang"; fi - - which $CXX - - which $CC - - $CXX --version - - cmake --version - -script: - - cd ${TRAVIS_BUILD_DIR} - - mkdir -p build && pushd build - - > - cmake --warn-uninitialized --debug-output - -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON - -DHIGHFIVE_TEST_SINGLE_INCLUDES:BOOL=ON - -DHIGHFIVE_PARALLEL_HDF5:BOOL=${HIGHFIVE_PARALLEL_HDF5} - -DHIGHFIVE_USE_EIGEN:BOOL=ON - -DHIGHFIVE_USE_XTENSOR:BOOL=${HIGHFIVE_USE_XTENSOR} - -DHIGHFIVE_USE_OPENCV:BOOL=${HIGHFIVE_USE_OPENCV} - -G "${CMAKE_GENERATOR}" ../ - - cmake --build . - - CTEST_OUTPUT_ON_FAILURE=1 cmake --build . --target ${TESTS_TARGET} - - popd - - if [ $IS_BASE_ENVIRON ]; then - bash tests/test_project_integration.sh; - fi diff --git a/packages/HighFive/CMakeLists.txt b/packages/HighFive/CMakeLists.txt index 7060fe713..2358e4172 100644 --- a/packages/HighFive/CMakeLists.txt +++ b/packages/HighFive/CMakeLists.txt @@ -47,10 +47,18 @@ else() set(HIGHFIVE_EXTRAS_DEFAULT OFF) endif() +if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) + include(CheckIncludeFileCXX) + CHECK_INCLUDE_FILE_CXX(span HIGHFIVE_TEST_SPAN_DEFAULT) +else() + set(HIGHFIVE_TEST_SPAN_DEFAULT Off) +endif() + option(HIGHFIVE_UNIT_TESTS "Compile unit-tests" ${HIGHFIVE_EXTRAS_DEFAULT}) option(HIGHFIVE_EXAMPLES "Compile examples" ${HIGHFIVE_EXTRAS_DEFAULT}) option(HIGHFIVE_BUILD_DOCS "Build documentation" ${HIGHFIVE_EXTRAS_DEFAULT}) +option(HIGHFIVE_TEST_SPAN "Enable std::span testing, requires C++20" ${HIGHFIVE_TEST_SPAN_DEFAULT}) option(HIGHFIVE_TEST_BOOST "Enable Boost testing" OFF) option(HIGHFIVE_TEST_EIGEN "Enable Eigen testing" OFF) option(HIGHFIVE_TEST_OPENCV "Enable OpenCV testing" OFF) @@ -60,7 +68,7 @@ option(HIGHFIVE_TEST_HALF_FLOAT "Enable half-precision floats" OFF) # TODO remove entirely. option(HIGHFIVE_HAS_CONCEPTS "Print readable compiler errors w/ C++20 concepts" OFF) -set(HIGHFIVE_MAX_ERROR 0 "Maximum number of compiler errors.") +set(HIGHFIVE_MAX_ERRORS 0 CACHE STRING "Maximum number of compiler errors.") option(HIGHFIVE_HAS_WERROR "Convert warnings to errors." OFF) option(HIGHFIVE_GLIBCXX_ASSERTIONS "Enable bounds check for STL." OFF) # TODO these some magic to get a drop down menu in ccmake @@ -69,15 +77,16 @@ mark_as_advanced(HIGHFIVE_SANITIZER) # Check compiler cxx_std requirements # ----------------------------------- +set(HIGHFIVE_CXX_STANDARD_DEFAULT 14) if(NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_STANDARD ${HIGHFIVE_CXX_STANDARD_DEFAULT}) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) endif() -if(CMAKE_CXX_STANDARD EQUAL 98 OR CMAKE_CXX_STANDARD LESS 14) - message(FATAL_ERROR "HighFive needs to be compiled with at least C++14") +if(CMAKE_CXX_STANDARD EQUAL 98 OR CMAKE_CXX_STANDARD LESS ${HIGHFIVE_CXX_STANDARD_DEFAULT}) + message(FATAL_ERROR "HighFive needs to be compiled with at least C++${HIGHFIVE_CXX_STANDARD_DEFAULT}") endif() add_compile_definitions(HIGHFIVE_CXX_STD=${CMAKE_CXX_STANDARD}) diff --git a/packages/HighFive/README.md b/packages/HighFive/README.md index 8e04eb13c..b8d71b357 100644 --- a/packages/HighFive/README.md +++ b/packages/HighFive/README.md @@ -48,7 +48,7 @@ It integrates nicely with other CMake projects by defining (and exporting) a Hig ### Known flaws - HighFive is not thread-safe. At best it has the same limitations as the HDF5 library. However, HighFive objects modify their members without protecting these writes. Users have reported that HighFive is not thread-safe even when using the threadsafe HDF5 library, e.g., https://github.com/BlueBrain/HighFive/discussions/675. -- Eigen support in core HighFive is broken. See https://github.com/BlueBrain/HighFive/issues/532. H5Easy is not +- Eigen support in core HighFive was broken until v3.0. See https://github.com/BlueBrain/HighFive/issues/532. H5Easy was not affected. - The support of fixed length strings isn't ideal. diff --git a/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake b/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake index 1b27edd10..861b80641 100644 --- a/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake +++ b/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake @@ -37,6 +37,13 @@ if(NOT TARGET HighFiveOpenCVDependency) endif() endif() +if(NOT TARGET HighFiveSpanDependency) + add_library(HighFiveSpanDependency INTERFACE) + if(HIGHFIVE_TEST_SPAN) + target_compile_definitions(HighFiveSpanDependency INTERFACE HIGHFIVE_TEST_SPAN=1) + endif() +endif() + if(NOT TARGET HighFiveOptionalDependencies) add_library(HighFiveOptionalDependencies INTERFACE) target_link_libraries(HighFiveOptionalDependencies INTERFACE @@ -44,5 +51,6 @@ if(NOT TARGET HighFiveOptionalDependencies) HighFiveEigenDependency HighFiveXTensorDependency HighFiveOpenCVDependency + HighFiveSpanDependency ) endif() diff --git a/packages/HighFive/cmake/HighFiveWarnings.cmake b/packages/HighFive/cmake/HighFiveWarnings.cmake index 3f569d5d5..a1dee19dc 100644 --- a/packages/HighFive/cmake/HighFiveWarnings.cmake +++ b/packages/HighFive/cmake/HighFiveWarnings.cmake @@ -24,33 +24,35 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" -Wconversion -Wsign-conversion ) +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_options(HighFiveWarnings + INTERFACE + -Wpedantic + -Wcast-align + -Wdouble-promotion + ) + + target_compile_options(HighFiveWarnings + INTERFACE + -ftemplate-backtrace-limit=0 + ) - if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Intel") + if(HIGHFIVE_HAS_WERROR) target_compile_options(HighFiveWarnings INTERFACE - -Wpedantic - -Wcast-align - -Wdouble-promotion + -Werror + -Wno-error=deprecated-declarations ) + endif() +endif() - target_compile_options(HighFiveWarnings +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if(HIGHFIVE_MAX_ERRORS) + target_compile_options(HighFiveFlags INTERFACE - -ftemplate-backtrace-limit=0 + -fmax-errors=${HIGHFIVE_MAX_ERRORS} ) - - if(HIGHFIVE_MAX_ERRORS) - target_compile_options(HighFiveFlags - INTERFACE - -fmax-errors=${HIGHFIVE_MAX_ERRORS} - ) - endif() - - if(HIGHFIVE_HAS_WERROR) - target_compile_options(HighFiveWarnings - INTERFACE - -Werror - -Wno-error=deprecated-declarations - ) - endif() endif() endif() diff --git a/packages/HighFive/doc/developer_guide.md b/packages/HighFive/doc/developer_guide.md index 13e360fc3..0f7af3e01 100644 --- a/packages/HighFive/doc/developer_guide.md +++ b/packages/HighFive/doc/developer_guide.md @@ -25,7 +25,7 @@ ctest --test-dir build You might want to add: * `-DHIGHFIVE_TEST_BOOST=On` or other optional dependencies on, -* `-DHIGHFIVE_MAX_ERROR=3` to only show the first three errors. +* `-DHIGHFIVE_MAX_ERRORS=3` to only show the first three errors. Generic CMake reminders: * `-DCMAKE_INSTALL_PREFIX` defines where HighFive will be installed, @@ -195,6 +195,9 @@ Write-read cycles for scalar values should be implemented in Unit-tests related to checking that `DataType` API, go in `tests/unit/tests_high_data_type.cpp`. +#### Empty Arrays +Check related to empty arrays to in `tests/unit/test_empty_arrays.cpp`. + #### Selections Anything selection related goes in `tests/unit/test_high_five_selection.cpp`. This includes things like `ElementSet` and `HyperSlab`. @@ -204,7 +207,7 @@ Regular write-read cycles for strings are performed along with the other types, see above. This should cover compatibility of `std::string` with all containers. However, additional testing is required, e.g. character set, padding, fixed vs. variable length. These all go in -`tests/unit/test_high_five_string.cpp`. +`tests/unit/test_string.cpp`. #### Specific Tests For Optional Containers If containers, e.g. `Eigen::Matrix` require special checks those go in files diff --git a/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css b/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css index a44945b36..a2715e268 100644 --- a/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css +++ b/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css @@ -1046,7 +1046,7 @@ blockquote::after { blockquote p { margin: var(--spacing-small) 0 var(--spacing-medium) 0; } -.paramname { +.paramname, .paramname em { font-weight: 600; color: var(--primary-dark-color); } @@ -1096,7 +1096,7 @@ div.contents .toc { border: 0; border-left: 1px solid var(--separator-color); border-radius: 0; - background-color: transparent; + background-color: var(--page-background-color); box-shadow: none; position: sticky; top: var(--toc-sticky-top); @@ -1988,14 +1988,16 @@ hr { } .contents hr { - box-shadow: 100px 0 0 var(--separator-color), - -100px 0 0 var(--separator-color), - 500px 0 0 var(--separator-color), - -500px 0 0 var(--separator-color), - 1500px 0 0 var(--separator-color), - -1500px 0 0 var(--separator-color), - 2000px 0 0 var(--separator-color), - -2000px 0 0 var(--separator-color); + box-shadow: 100px 0 var(--separator-color), + -100px 0 var(--separator-color), + 500px 0 var(--separator-color), + -500px 0 var(--separator-color), + 900px 0 var(--separator-color), + -900px 0 var(--separator-color), + 1400px 0 var(--separator-color), + -1400px 0 var(--separator-color), + 1900px 0 var(--separator-color), + -1900px 0 var(--separator-color); } .contents img, .contents .center, .contents center, .contents div.image object { diff --git a/packages/HighFive/doc/migration_guide.md b/packages/HighFive/doc/migration_guide.md index 2ffe9e257..ee72e52c7 100644 --- a/packages/HighFive/doc/migration_guide.md +++ b/packages/HighFive/doc/migration_guide.md @@ -15,7 +15,6 @@ replaced with an `std::vector<std::string>` (for example). If desired one can silence warnings by replacing `FixedLenStringArray` with `deprecated::FixedLenStringArray`. - ## Deprecation of `read(T*, ...)`. A "raw read" is when the user allocates sufficient bytes and provides HighFive with the pointer to the first byte. "Regular reads" take a detour via the @@ -40,7 +39,7 @@ dset.read(x); which is fine because is a contiguous sequence of doubles. It's equivalent to following `v3` code: ``` -double x[2][3]; +double x[n][m]; dset.read_raw((double*) x); ``` @@ -48,11 +47,11 @@ dset.read_raw((double*) x); We consider the example above to be accidentally using a raw read, when it could be performing a regular read. We suggest to not change the above, i.e. ``` -double x[2][3]; +double x[n][m]; dset.read(x); ``` continues to be correct in `v3` and can check that the dimensions match. The -inspector recognizes `double[2][3]` as a contiguous array of doubles. +inspector recognizes `double[n][m]` as a contiguous array of doubles. Therefore, it'll use the shallow-copy buffer and avoid the any additional allocations or copies. @@ -61,11 +60,48 @@ When genuinely performing a "raw read", one must replace `read` with `read_raw`. For example: ``` -double* x = malloc(2*3 * sizeof(double)); +double* x = malloc(n*m * sizeof(double)); dset.read_raw(x); ``` is correct in `v3`. +## Change for `T**`, `T***`, etc. +*The immediately preceding section is likely relevant.* + +In `v2` raw pointers could be used to indicate dimensionality. For example: +``` +double* x = malloc(n*m * sizeof(double)); +auto dset = file.createDataSet("foo", DataSpace({n, m}), ...); + +dset.write((double**) x); +dset.read((double**) x); +``` +was valid and would write the flat array `x` into the two-dimensional dataset +`"foo"`. This must be modernized as follows: +``` +double* x = malloc(n*m * sizeof(double)); +auto dset = file.createDataSet("foo", DataSpace({n, m}), ...); + +dset.write_raw(x); +dset.read_raw(x); +``` + +In `v3` the type `T**` will refer a pointer to a pointer (as usual). The +following: +``` +size_t n = 2, m = 3; +double** x = malloc(n * sizeof(double*)); +for(size_t i = 0; i < n; ++i) { + x[i] = malloc(m * sizeof(double)); +} + +auto dset = file.createDataSet("foo", DataSpace({n, m}), ...); +dset.write(x); +dset.read(x); +``` +is correct in `v3` but would probably segfault in `v2`. + + ## Reworked CMake In `v3` we completely rewrote the CMake code of HighFive. Since HighFive is a header only library, it needs to perform two tasks: @@ -132,3 +168,71 @@ We felt that the savings in typing effort weren't worth introducing the concept of a "file driver". Removing the concept hopefully makes it easier to add a better abstraction for the handling of the property lists, when we discover such an abstraction. + +## Removal of broadcasting +HighFive v2 had a feature that a dataset (or attribute) of shape `[n, 1]` could +be read into a one-dimensional array automatically. + +The feature is prone to accidentally not failing. Consider an array that shape +`[n, m]` and in general both `n, m > 0`. Hence, one should always be reading +into a two-dimensional array, even if `n == 1` or `m == 1`. However, due to +broadcasting, if one of the dimensions (accidentally) happens to be one, then +the checks wont fails. This isn't a bug, however, it can hide a bug. For +example if the test happen to use `[n, 1]` datasets and a one-dimensional +array. + +Broadcasting in HighFive was different from broadcasting in NumPy. For reading +into one-dimensional data HighFive supports stripping all dimensions that are +not `1`. When extending the feature to multi-dimensional arrays it gets tricky. +We can't strip from both the front and back. If we allow stripping from both +ends, arrays such as `[1, n, m]` read into `[n, m]` if `m > 1` but into `[1, +n]` (instead of `[n, 1]`) if (coincidentally) `m == 1`. For HighFive because +avoiding being forced to read `[n, 1]` into `std::vector<std::vector<T>>` is +more important than `[1, n]`. Flattening the former requires copying +everything while the latter can be made flat by just accessing the first value. +Therefore, HighFive had a preference to strip from the right, while NumPy adds +`1`s to the front/left of the shape. + +In `v3` we've removed broadcasting. Instead users must use one of the two +alternatives: squeezing and reshaping. The examples show will use datasets and +reading, but it works the same for attributes and writing. + +### Squeezing +Often we know that the `k`th dimension is `1`, e.g. a column is `[n, 1]` and a +row is `[1, m]`. In this case it's convenient to state, remove dimension `k`. +The syntax to simultaneously remove the dimensions `{0, 2}` is: + +``` +dset.squeezeMemSpace({0, 2}).read(array); +``` +Which will read a dataset with dimensions `[1, n, 1]` into an array of shape +`[n]`. + +### Reshape +Sometimes it's easier to state what the new shape must be. For this we have the +syntax: +``` +dset.reshapeMemSpace(dims).read(array); +``` +To declare that `array` should have dimensions `dims` even if +`dset.getDimensions()` is something different. + +Example: +``` +dset.reshapeMemSpace({dset.getElementCount()}).read(array); +``` +to read into a one-dimensional array. + +### Scalars +There's a safe case that seems needlessly strict to enforce: if the dataset is +a multi-dimensional array with one element one should be able to read into +(write from) a scalar. + +The reverse, i.e. reading a scalar value in the HDF5 file into a +multi-dimensional array isn't supported, because if we want to support array +with runtime-defined rank, we can't deduce the correct shape, e.g. `[1]` vs. +`[1, 1, 1]`, when read into an array. + +# Removal of `Object*Props`. +To out knowledge these could not be used meaningfully. Please create an issue +if you relied on these. diff --git a/packages/HighFive/include/highfive/H5Attribute.hpp b/packages/HighFive/include/highfive/H5Attribute.hpp index c34f9e49f..9fa3b63ba 100644 --- a/packages/HighFive/include/highfive/H5Attribute.hpp +++ b/packages/HighFive/include/highfive/H5Attribute.hpp @@ -13,6 +13,7 @@ #include <H5Apublic.h> #include "H5DataType.hpp" +#include "H5DataSpace.hpp" #include "H5Object.hpp" #include "bits/H5Friends.hpp" #include "bits/H5Path_traits.hpp" @@ -70,7 +71,7 @@ class Attribute: public Object, public PathTraits<Attribute> { /// \since 1.0 DataType getDataType() const; - /// \brief Get the DataSpace of the current Attribute. + /// \brief Get a copy of the DataSpace of the current Attribute. /// \code{.cpp} /// Attribute attr = dset.createAttribute<int>("foo", DataSpace(1, 2)); /// auto dspace = attr.getSpace(); // This will be a DataSpace of dimension 1 * 2 @@ -78,8 +79,12 @@ class Attribute: public Object, public PathTraits<Attribute> { /// \since 1.0 DataSpace getSpace() const; - /// \brief Get the DataSpace of the current Attribute. - /// \note This is an alias of getSpace(). + /// \brief Get the memory DataSpace of the current Attribute. + /// + /// HDF5 attributes don't support selections. Therefore, there's no need + /// for a memory dataspace. However, HighFive supports allocating arrays + /// and checking dimensions, this requires the dimensions of the memspace. + /// /// \since 1.0 DataSpace getMemSpace() const; @@ -245,10 +250,34 @@ class Attribute: public Object, public PathTraits<Attribute> { // No empty attributes Attribute() = delete; + /// + /// \brief Return an `Attribute` with `axes` squeezed from the memspace. + /// + /// Returns an `Attribute` in which the memspace has been modified + /// to not include the axes listed in `axes`. + /// + /// Throws if any axis to be squeezes has a dimension other than `1`. + /// + /// \since 3.0 + Attribute squeezeMemSpace(const std::vector<size_t>& axes) const; + + /// + /// \brief Return a `Attribute` with a simple memspace with `dims`. + /// + /// Returns a `Attribute` in which the memspace has been modified + /// to be a simple dataspace with dimensions `dims`. + /// + /// Throws if the number of elements changes. + /// + /// \since 3.0 + Attribute reshapeMemSpace(const std::vector<size_t>& dims) const; + protected: using Object::Object; private: + DataSpace _mem_space; + #if HIGHFIVE_HAS_FRIEND_DECLARATIONS template <typename Derivate> friend class ::HighFive::AnnotateTraits; diff --git a/packages/HighFive/include/highfive/H5File.hpp b/packages/HighFive/include/highfive/H5File.hpp index a8db5f2a1..b134aaa49 100644 --- a/packages/HighFive/include/highfive/H5File.hpp +++ b/packages/HighFive/include/highfive/H5File.hpp @@ -70,7 +70,7 @@ class File: public Object, public NodeTraits<File>, public AnnotateTraits<File> /// /// \brief Return the name of the file /// - const std::string& getName() const noexcept; + const std::string& getName() const; /// \brief Object path of a File is always "/" diff --git a/packages/HighFive/include/highfive/H5Object.hpp b/packages/HighFive/include/highfive/H5Object.hpp index 4cf4e7de0..b6058bf78 100644 --- a/packages/HighFive/include/highfive/H5Object.hpp +++ b/packages/HighFive/include/highfive/H5Object.hpp @@ -31,34 +31,12 @@ enum class ObjectType { Other // Internal/custom object type }; -namespace detail { -/// \brief Internal hack to create an `Object` from an ID. -/// -/// WARNING: Creating an Object from an ID has implications w.r.t. the lifetime of the object -/// that got passed via its ID. Using this method careless opens up the suite of issues -/// related to C-style resource management, including the analog of double free, dangling -/// pointers, etc. -/// -/// NOTE: This is not part of the API and only serves to work around a compiler issue in GCC which -/// prevents us from using `friend`s instead. This function should only be used for internal -/// purposes. The problematic construct is: -/// -/// template<class Derived> -/// friend class SomeCRTP<Derived>; -/// -/// \private -Object make_object(hid_t hid); -} // namespace detail - class Object { public: // move constructor, reuse hid Object(Object&& other) noexcept; - // decrease reference counter - ~Object(); - /// /// \brief isValid /// \return true if current Object is a valid HDF5Object @@ -99,13 +77,15 @@ class Object { // Init with an low-level object id explicit Object(hid_t); + // decrease reference counter + ~Object(); + // Copy-Assignment operator Object& operator=(const Object& other); hid_t _hid; private: - friend Object detail::make_object(hid_t); friend class Reference; friend class CompoundType; diff --git a/packages/HighFive/include/highfive/H5PropertyList.hpp b/packages/HighFive/include/highfive/H5PropertyList.hpp index 2368f5ca9..5d467dbb3 100644 --- a/packages/HighFive/include/highfive/H5PropertyList.hpp +++ b/packages/HighFive/include/highfive/H5PropertyList.hpp @@ -87,7 +87,6 @@ namespace HighFive { /// \brief Types of property lists /// enum class PropertyType : int { - OBJECT_CREATE, FILE_CREATE, FILE_ACCESS, DATASET_CREATE, @@ -99,7 +98,6 @@ enum class PropertyType : int { DATATYPE_ACCESS, STRING_CREATE, ATTRIBUTE_CREATE, - OBJECT_COPY, LINK_CREATE, LINK_ACCESS, }; @@ -195,7 +193,6 @@ class PropertyList: public PropertyListBase { void _initializeIfNeeded(); }; -using ObjectCreateProps = PropertyList<PropertyType::OBJECT_CREATE>; using FileCreateProps = PropertyList<PropertyType::FILE_CREATE>; using FileAccessProps = PropertyList<PropertyType::FILE_ACCESS>; using DataSetCreateProps = PropertyList<PropertyType::DATASET_CREATE>; @@ -207,7 +204,6 @@ using DataTypeCreateProps = PropertyList<PropertyType::DATATYPE_CREATE>; using DataTypeAccessProps = PropertyList<PropertyType::DATATYPE_ACCESS>; using StringCreateProps = PropertyList<PropertyType::STRING_CREATE>; using AttributeCreateProps = PropertyList<PropertyType::ATTRIBUTE_CREATE>; -using ObjectCopyProps = PropertyList<PropertyType::OBJECT_COPY>; using LinkCreateProps = PropertyList<PropertyType::LINK_CREATE>; using LinkAccessProps = PropertyList<PropertyType::LINK_ACCESS>; @@ -606,7 +602,6 @@ class CreateIntermediateGroup { public: explicit CreateIntermediateGroup(bool create = true); - explicit CreateIntermediateGroup(const ObjectCreateProps& ocpl); explicit CreateIntermediateGroup(const LinkCreateProps& lcpl); bool isSet() const; @@ -615,7 +610,6 @@ class CreateIntermediateGroup { void fromPropertyList(hid_t hid); private: - friend ObjectCreateProps; friend LinkCreateProps; void apply(hid_t hid) const; bool _create; diff --git a/packages/HighFive/include/highfive/H5Selection.hpp b/packages/HighFive/include/highfive/H5Selection.hpp index c00c66d52..27681fe7a 100644 --- a/packages/HighFive/include/highfive/H5Selection.hpp +++ b/packages/HighFive/include/highfive/H5Selection.hpp @@ -30,21 +30,21 @@ class Selection: public SliceTraits<Selection> { /// \brief getSpace /// \return Dataspace associated with this selection /// - DataSpace getSpace() const noexcept; + DataSpace getSpace() const; /// /// \brief getMemSpace /// \return Dataspace associated with the memory representation of this /// selection /// - DataSpace getMemSpace() const noexcept; + DataSpace getMemSpace() const; /// /// \brief getDataSet /// \return parent dataset of this selection /// - DataSet& getDataset() noexcept; - const DataSet& getDataset() const noexcept; + DataSet& getDataset(); + const DataSet& getDataset() const; /// /// \brief return the datatype of the selection diff --git a/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp b/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp index 19eceb49f..7ad38699a 100644 --- a/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp @@ -8,6 +8,7 @@ */ #pragma once +#include <H5Ipublic.h> #include <algorithm> #include <functional> #include <numeric> @@ -18,10 +19,13 @@ #include "../H5DataSpace.hpp" #include "H5Converter_misc.hpp" +#include "H5Inspector_misc.hpp" #include "H5ReadWrite_misc.hpp" #include "H5Utils.hpp" #include "h5a_wrapper.hpp" #include "h5d_wrapper.hpp" +#include "squeeze.hpp" +#include "assert_compatible_spaces.hpp" namespace HighFive { @@ -51,7 +55,7 @@ inline DataSpace Attribute::getSpace() const { } inline DataSpace Attribute::getMemSpace() const { - return getSpace(); + return _mem_space.getId() == H5I_INVALID_HID ? getSpace() : _mem_space; } template <typename T> @@ -70,19 +74,17 @@ inline void Attribute::read(T& array) const { [this]() -> std::string { return this->getName(); }, details::BufferInfo<T>::Operation::read); - if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) { + if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) { std::ostringstream ss; - ss << "Impossible to read Attribute of dimensions " << mem_space.getNumberDimensions() - << " into arrays of dimensions " << buffer_info.n_dimensions; + ss << "Impossible to read attribute of dimensions " << mem_space.getNumberDimensions() + << " into arrays of dimensions: " << buffer_info.getMinRank() << "(min) to " + << buffer_info.getMaxRank() << "(max)"; 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); + details::inspector<T>::prepare(array, dims); return; } @@ -137,10 +139,11 @@ inline void Attribute::write(const T& buffer) { [this]() -> std::string { return this->getName(); }, details::BufferInfo<T>::Operation::write); - if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) { + if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) { std::ostringstream ss; - ss << "Impossible to write buffer of dimensions " << buffer_info.n_dimensions - << " into dataset of dimensions " << mem_space.getNumberDimensions(); + ss << "Impossible to write attribute of dimensions " << mem_space.getNumberDimensions() + << " into arrays of dimensions: " << buffer_info.getMinRank() << "(min) to " + << buffer_info.getMaxRank() << "(max)"; throw DataSpaceException(ss.str()); } auto w = details::data_converter::serialize<T>(buffer, dims, file_datatype); @@ -160,4 +163,21 @@ inline void Attribute::write_raw(const T* buffer) { write_raw(buffer, mem_datatype); } +inline Attribute Attribute::squeezeMemSpace(const std::vector<size_t>& axes) const { + auto mem_dims = this->getMemSpace().getDimensions(); + auto squeezed_dims = detail::squeeze(mem_dims, axes); + + auto attr = *this; + attr._mem_space = DataSpace(squeezed_dims); + return attr; +} + +inline Attribute Attribute::reshapeMemSpace(const std::vector<size_t>& new_dims) const { + detail::assert_compatible_spaces(this->getMemSpace(), new_dims); + + auto attr = *this; + attr._mem_space = DataSpace(new_dims); + return attr; +} + } // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp b/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp index d1ba132c4..5fcbafb5e 100644 --- a/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp @@ -415,10 +415,8 @@ struct data_converter { static Reader<T> get_reader(const std::vector<size_t>& dims, T& val, const DataType& file_datatype) { - // TODO Use bufferinfo for recursive_ndim - auto effective_dims = details::squeezeDimensions(dims, inspector<T>::recursive_ndim); - inspector<T>::prepare(val, effective_dims); - return Reader<T>(effective_dims, val, file_datatype); + inspector<T>::prepare(val, dims); + return Reader<T>(dims, val, file_datatype); } }; diff --git a/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp b/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp index 4321a4658..797702188 100644 --- a/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp @@ -226,9 +226,9 @@ inline EnumType<details::Boolean> create_enum_boolean() { // Other cases not supported. Fail early with a user message template <typename T> AtomicType<T>::AtomicType() { - static_assert(details::inspector<T>::recursive_ndim == 0, - "Atomic types cant be arrays, except for char[] (fixed-length strings)"); - static_assert(details::inspector<T>::recursive_ndim > 0, "Type not supported"); + static_assert( + true, + "Missing specialization of AtomicType<T>. Therefore, type T is not supported by HighFive."); } diff --git a/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp b/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp index ceae1e531..4382c14c1 100644 --- a/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp @@ -131,9 +131,10 @@ inline DataSpace DataSpace::FromCharArrayStrings(const char (&)[N][Width]) { namespace details { -/// dimension checks @internal -inline bool checkDimensions(const DataSpace& mem_space, size_t n_dim_requested) { - return checkDimensions(mem_space.getDimensions(), n_dim_requested); +inline bool checkDimensions(const DataSpace& mem_space, + size_t min_dim_requested, + size_t max_dim_requested) { + return checkDimensions(mem_space.getDimensions(), min_dim_requested, max_dim_requested); } } // namespace details diff --git a/packages/HighFive/include/highfive/bits/H5File_misc.hpp b/packages/HighFive/include/highfive/bits/H5File_misc.hpp index 52ae59516..6013953b1 100644 --- a/packages/HighFive/include/highfive/bits/H5File_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5File_misc.hpp @@ -82,7 +82,7 @@ inline File::File(const std::string& filename, _hid = detail::h5f_create(filename.c_str(), createMode, fcpl, fapl); } -inline const std::string& File::getName() const noexcept { +inline const std::string& File::getName() const { if (_filename.empty()) { _filename = details::get_name([this](char* buffer, size_t length) { return detail::h5f_get_name(getId(), buffer, length); diff --git a/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp b/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp index 434545a60..24b547e21 100644 --- a/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp +++ b/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp @@ -1,20 +1,12 @@ #pragma once -#include <cstddef> -#include <numeric> -#include <functional> -#include <vector> +#include "compute_total_size.hpp" namespace HighFive { -inline size_t compute_total_size(const std::vector<size_t>& dims) { - return std::accumulate(dims.begin(), dims.end(), size_t{1u}, std::multiplies<size_t>()); -} - template <typename T> using unqualified_t = typename std::remove_const<typename std::remove_reference<T>::type>::type; - namespace details { template <typename T> diff --git a/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp b/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp index 3f69276c4..59bf85422 100644 --- a/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp @@ -27,93 +27,20 @@ namespace HighFive { namespace details { -inline bool checkDimensions(const std::vector<size_t>& dims, size_t n_dim_requested) { - size_t n_dim_actual = dims.size(); - - // We should allow reading scalar from shapes like `(1, 1, 1)`. - if (n_dim_requested == 0) { - if (n_dim_actual == 0ul) { - return true; - } - - return size_t(std::count(dims.begin(), dims.end(), 1ul)) == n_dim_actual; - } - - // For non-scalar datasets, we can squeeze away singleton dimension, but - // we never add any. - if (n_dim_actual < n_dim_requested) { - return false; - } - - // Special case for 1-dimensional arrays, which can squeeze `1`s from either - // side simultaneously if needed. - if (n_dim_requested == 1ul) { - return n_dim_actual >= 1ul && - size_t(std::count(dims.begin(), dims.end(), 1ul)) >= n_dim_actual - 1ul; +inline bool checkDimensions(const std::vector<size_t>& dims, + size_t min_dim_requested, + size_t max_dim_requested) { + if (min_dim_requested <= dims.size() && dims.size() <= max_dim_requested) { + return true; } - // All other cases strip front only. This avoid unstable behaviour when - // squeezing singleton dimensions. - size_t n_dim_excess = n_dim_actual - n_dim_requested; - bool squeeze_back = true; - for (size_t i = 1; i <= n_dim_excess; ++i) { - if (dims[n_dim_actual - i] != 1) { - squeeze_back = false; - break; - } - } - - return squeeze_back; + // Scalar values still support broadcasting + // into arrays with one element. + size_t n_elements = compute_total_size(dims); + return n_elements == 1 && min_dim_requested == 0; } - -inline std::vector<size_t> squeezeDimensions(const std::vector<size_t>& dims, - size_t n_dim_requested) { - auto format_error_message = [&]() -> std::string { - return "Can't interpret dims = " + format_vector(dims) + " as " + - std::to_string(n_dim_requested) + "-dimensional."; - }; - - if (n_dim_requested == 0) { - if (!checkDimensions(dims, n_dim_requested)) { - throw std::invalid_argument("Failed dimensions check: " + format_error_message()); - } - - return {1ul}; - } - - auto n_dim = dims.size(); - if (n_dim < n_dim_requested) { - throw std::invalid_argument("Failed 'n_dim < n_dim_requested: " + format_error_message()); - } - - if (n_dim_requested == 1ul) { - size_t non_singleton_dim = size_t(-1); - for (size_t i = 0; i < n_dim; ++i) { - if (dims[i] != 1ul) { - if (non_singleton_dim == size_t(-1)) { - non_singleton_dim = i; - } else { - throw std::invalid_argument("Failed one-dimensional: " + - format_error_message()); - } - } - } - - return {dims[std::min(non_singleton_dim, n_dim - 1)]}; - } - - size_t n_dim_excess = dims.size() - n_dim_requested; - for (size_t i = 1; i <= n_dim_excess; ++i) { - if (dims[n_dim - i] != 1) { - throw std::invalid_argument("Failed stripping from back:" + format_error_message()); - } - } - - return std::vector<size_t>(dims.begin(), - dims.end() - static_cast<std::ptrdiff_t>(n_dim_excess)); -} } // namespace details @@ -125,13 +52,15 @@ inspector<T> { // hdf5_type is the base read by hdf5 (c-type) (e.g. std::vector<std::string> => const char*) using hdf5_type - // Number of dimensions starting from here - static constexpr size_t recursive_ndim // Is the inner type trivially copyable for optimisation // If this value is true: data() is mandatory // If this value is false: serialize, unserialize are mandatory static constexpr bool is_trivially_copyable + // Is this type trivially nestable, i.e. is type[n] a contiguous + // array of `base_type[N]`? + static constexpr bool is_trivially_nestable + // Reading: // Allocate the value following dims (should be recursive) static void prepare(type& val, const std::vector<std::size_t> dims) @@ -160,8 +89,15 @@ struct type_helper { using hdf5_type = base_type; static constexpr size_t ndim = 0; - static constexpr size_t recursive_ndim = ndim; + static constexpr size_t min_ndim = ndim; + static constexpr size_t max_ndim = ndim; + static constexpr bool is_trivially_copyable = std::is_trivially_copyable<type>::value; + static constexpr bool is_trivially_nestable = is_trivially_copyable; + + static size_t getRank(const type& /* val */) { + return ndim; + } static std::vector<size_t> getDimensions(const type& /* val */) { return {}; @@ -206,6 +142,7 @@ struct inspector<bool>: type_helper<bool> { using hdf5_type = int8_t; static constexpr bool is_trivially_copyable = false; + static constexpr bool is_trivially_nestable = false; static hdf5_type* data(type& /* val */) { throw DataSpaceException("A boolean cannot be read directly."); @@ -255,6 +192,7 @@ struct inspector<Reference>: type_helper<Reference> { using hdf5_type = hobj_ref_t; static constexpr bool is_trivially_copyable = false; + static constexpr bool is_trivially_nestable = false; static hdf5_type* data(type& /* val */) { throw DataSpaceException("A Reference cannot be read directly."); @@ -285,16 +223,27 @@ struct inspector<std::vector<T>> { using hdf5_type = typename inspector<value_type>::hdf5_type; static constexpr size_t ndim = 1; - static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim; + static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim; + static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value && - inspector<value_type>::is_trivially_copyable; + inspector<value_type>::is_trivially_nestable; + static constexpr bool is_trivially_nestable = false; + + static size_t getRank(const type& val) { + if (!val.empty()) { + return ndim + inspector<value_type>::getRank(val[0]); + } else { + return min_ndim; + } + } static std::vector<size_t> getDimensions(const type& val) { - std::vector<size_t> sizes(recursive_ndim, 1ul); + auto rank = getRank(val); + std::vector<size_t> sizes(rank, 1ul); sizes[0] = val.size(); if (!val.empty()) { auto s = inspector<value_type>::getDimensions(val[0]); - assert(s.size() + ndim == sizes.size()); for (size_t i = 0; i < s.size(); ++i) { sizes[i + ndim] = s[i]; } @@ -348,8 +297,15 @@ struct inspector<std::vector<bool>> { using hdf5_type = uint8_t; static constexpr size_t ndim = 1; - static constexpr size_t recursive_ndim = ndim; + static constexpr size_t min_ndim = ndim; + static constexpr size_t max_ndim = ndim; + static constexpr bool is_trivially_copyable = false; + static constexpr bool is_trivially_nestable = false; + + static size_t getRank(const type& /* val */) { + return ndim; + } static std::vector<size_t> getDimensions(const type& val) { std::vector<size_t> sizes{val.size()}; @@ -394,17 +350,22 @@ struct inspector<std::array<T, N>> { using hdf5_type = typename inspector<value_type>::hdf5_type; static constexpr size_t ndim = 1; - static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim; + static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim; + static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value && - sizeof(type) == N * sizeof(T) && - inspector<value_type>::is_trivially_copyable; + inspector<value_type>::is_trivially_nestable; + static constexpr bool is_trivially_nestable = (sizeof(type) == N * sizeof(T)) && + is_trivially_copyable; + + static size_t getRank(const type& val) { + return ndim + inspector<value_type>::getRank(val[0]); + } static std::vector<size_t> getDimensions(const type& val) { std::vector<size_t> sizes{N}; - if (!val.empty()) { - auto s = inspector<value_type>::getDimensions(val[0]); - sizes.insert(sizes.end(), s.begin(), s.end()); - } + auto s = inspector<value_type>::getDimensions(val[0]); + sizes.insert(sizes.end(), s.begin(), s.end()); return sizes; } @@ -455,6 +416,7 @@ struct inspector<std::array<T, N>> { } }; + // Cannot be use for reading template <typename T> struct inspector<T*> { @@ -464,9 +426,20 @@ struct inspector<T*> { using hdf5_type = typename inspector<value_type>::hdf5_type; static constexpr size_t ndim = 1; - static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim; + static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim; + static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value && - inspector<value_type>::is_trivially_copyable; + inspector<value_type>::is_trivially_nestable; + static constexpr bool is_trivially_nestable = false; + + static size_t getRank(const type& val) { + if (val != nullptr) { + return ndim + inspector<value_type>::getRank(val[0]); + } else { + return min_ndim; + } + } static std::vector<size_t> getDimensions(const type& /* val */) { throw DataSpaceException("Not possible to have size of a T*"); @@ -494,9 +467,12 @@ struct inspector<T[N]> { using hdf5_type = typename inspector<value_type>::hdf5_type; static constexpr size_t ndim = 1; - static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim; + static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim; + static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value && - inspector<value_type>::is_trivially_copyable; + inspector<value_type>::is_trivially_nestable; + static constexpr bool is_trivially_nestable = is_trivially_copyable; static void prepare(type& val, const std::vector<size_t>& dims) { if (dims.size() < 1) { @@ -513,12 +489,14 @@ struct inspector<T[N]> { } } + static size_t getRank(const type& val) { + return ndim + inspector<value_type>::getRank(val[0]); + } + static std::vector<size_t> getDimensions(const type& val) { std::vector<size_t> sizes{N}; - if (N > 0) { - auto s = inspector<value_type>::getDimensions(val[0]); - sizes.insert(sizes.end(), s.begin(), s.end()); - } + auto s = inspector<value_type>::getDimensions(val[0]); + sizes.insert(sizes.end(), s.begin(), s.end()); return sizes; } @@ -541,5 +519,6 @@ struct inspector<T[N]> { } }; + } // namespace details } // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/H5Node_traits.hpp b/packages/HighFive/include/highfive/bits/H5Node_traits.hpp index 56d9f8d3a..8076bd9b4 100644 --- a/packages/HighFive/include/highfive/bits/H5Node_traits.hpp +++ b/packages/HighFive/include/highfive/bits/H5Node_traits.hpp @@ -217,9 +217,6 @@ class NodeTraits { // It makes behavior consistent among versions and by default transforms // errors to exceptions bool _exist(const std::string& node_name, bool raise_errors = true) const; - - // Opens an arbitrary object to obtain info - Object _open(const std::string& node_name) const; }; diff --git a/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp b/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp index 49cfc639d..b26257779 100644 --- a/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp @@ -254,7 +254,12 @@ inline LinkType NodeTraits<Derivate>::getLinkType(const std::string& node_name) template <typename Derivate> inline ObjectType NodeTraits<Derivate>::getObjectType(const std::string& node_name) const { - return _open(node_name).getType(); + const auto id = detail::h5o_open(static_cast<const Derivate*>(this)->getId(), + node_name.c_str(), + H5P_DEFAULT); + auto object_type = _convert_object_type(detail::h5i_get_type(id)); + detail::h5o_close(id); + return object_type; } @@ -314,13 +319,4 @@ inline void NodeTraits<Derivate>::createHardLink(const std::string& link_name, } -template <typename Derivate> -inline Object NodeTraits<Derivate>::_open(const std::string& node_name) const { - const auto id = detail::h5o_open(static_cast<const Derivate*>(this)->getId(), - node_name.c_str(), - H5P_DEFAULT); - return detail::make_object(id); -} - - } // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/H5Object_misc.hpp b/packages/HighFive/include/highfive/bits/H5Object_misc.hpp index c5a1f3999..eefddc1ed 100644 --- a/packages/HighFive/include/highfive/bits/H5Object_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5Object_misc.hpp @@ -15,12 +15,6 @@ #include "h5i_wrapper.hpp" namespace HighFive { -namespace detail { -inline Object make_object(hid_t hid) { - return Object(hid); -} -} // namespace detail - inline Object::Object() : _hid(H5I_INVALID_HID) {} diff --git a/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp b/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp index 1fa2101f2..cfeb7685d 100644 --- a/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp @@ -17,8 +17,6 @@ inline hid_t convert_plist_type(PropertyType propertyType) { // The HP5_XXX are macros with function calls so we can't assign // them as the enum values switch (propertyType) { - case PropertyType::OBJECT_CREATE: - return H5P_OBJECT_CREATE; case PropertyType::FILE_CREATE: return H5P_FILE_CREATE; case PropertyType::FILE_ACCESS: @@ -41,8 +39,6 @@ inline hid_t convert_plist_type(PropertyType propertyType) { return H5P_STRING_CREATE; case PropertyType::ATTRIBUTE_CREATE: return H5P_ATTRIBUTE_CREATE; - case PropertyType::OBJECT_COPY: - return H5P_OBJECT_COPY; case PropertyType::LINK_CREATE: return H5P_LINK_CREATE; case PropertyType::LINK_ACCESS: @@ -390,11 +386,6 @@ inline double Caching::getW0() const { inline CreateIntermediateGroup::CreateIntermediateGroup(bool create) : _create(create) {} -inline CreateIntermediateGroup::CreateIntermediateGroup(const ObjectCreateProps& ocpl) { - fromPropertyList(ocpl.getId()); -} - - inline void CreateIntermediateGroup::apply(const hid_t hid) const { detail::h5p_set_create_intermediate_group(hid, _create ? 1 : 0); } diff --git a/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp b/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp index 05bb49888..e5c862bc5 100644 --- a/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp @@ -9,6 +9,7 @@ #pragma once #include <H5Tpublic.h> +#include "H5Inspector_misc.hpp" #include "H5Utils.hpp" namespace HighFive { @@ -57,10 +58,14 @@ struct BufferInfo { template <class F> BufferInfo(const DataType& dtype, F getName, Operation _op); + size_t getRank(const T& array) const; + size_t getMinRank() const; + size_t getMaxRank() const; + // member data for info depending on the destination dataset type const bool is_fixed_len_string; - const size_t n_dimensions; const DataType data_type; + const size_t rank_correction; }; // details implementation @@ -135,10 +140,9 @@ BufferInfo<T>::BufferInfo(const DataType& file_data_type, F getName, Operation _ : op(_op) , is_fixed_len_string(file_data_type.isFixedLenStr()) // In case we are using Fixed-len strings we need to subtract one dimension - , n_dimensions(details::inspector<type_no_const>::recursive_ndim - - ((is_fixed_len_string && is_char_array) ? 1 : 0)) , data_type(string_type_checker<char_array_t>::getDataType(create_datatype<elem_type>(), - file_data_type)) { + file_data_type)) + , rank_correction((is_fixed_len_string && is_char_array) ? 1 : 0) { // We warn. In case they are really not convertible an exception will rise on read/write if (file_data_type.getClass() != data_type.getClass()) { HIGHFIVE_LOG_WARN(getName() + "\": data and hdf5 dataset have different types: " + @@ -157,6 +161,21 @@ BufferInfo<T>::BufferInfo(const DataType& file_data_type, F getName, Operation _ } } +template <typename T> +size_t BufferInfo<T>::getRank(const T& array) const { + return details::inspector<type_no_const>::getRank(array) - rank_correction; +} + +template <typename T> +size_t BufferInfo<T>::getMinRank() const { + return details::inspector<T>::min_ndim - rank_correction; +} + +template <typename T> +size_t BufferInfo<T>::getMaxRank() const { + return details::inspector<T>::max_ndim - rank_correction; +} + } // namespace details } // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp b/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp index c35b7bbf3..d1c14e930 100644 --- a/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp @@ -17,19 +17,19 @@ inline Selection::Selection(const DataSpace& memspace, , _file_space(file_space) , _set(set) {} -inline DataSpace Selection::getSpace() const noexcept { +inline DataSpace Selection::getSpace() const { return _file_space; } -inline DataSpace Selection::getMemSpace() const noexcept { +inline DataSpace Selection::getMemSpace() const { return _mem_space; } -inline DataSet& Selection::getDataset() noexcept { +inline DataSet& Selection::getDataset() { return _set; } -inline const DataSet& Selection::getDataset() const noexcept { +inline const DataSet& Selection::getDataset() const { return _set; } diff --git a/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp b/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp index fd8c31d27..e2b481a36 100644 --- a/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp +++ b/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp @@ -13,6 +13,7 @@ #include "H5_definitions.hpp" #include "H5Utils.hpp" +#include "convert_size_vector.hpp" #include "../H5PropertyList.hpp" #include "h5s_wrapper.hpp" @@ -51,17 +52,6 @@ class ElementSet { friend class SliceTraits; }; -namespace detail { - -template <class To, class From> -inline std::vector<To> convertSizeVector(const std::vector<From>& from) { - std::vector<To> to(from.size()); - std::copy(from.cbegin(), from.cend(), to.begin()); - - return to; -} -} // namespace detail - inline std::vector<hsize_t> toHDF5SizeVector(const std::vector<size_t>& from) { return detail::convertSizeVector<hsize_t>(from); } @@ -73,10 +63,10 @@ inline std::vector<size_t> toSTLSizeVector(const std::vector<hsize_t>& from) { struct RegularHyperSlab { RegularHyperSlab() = default; - RegularHyperSlab(std::vector<size_t> offset_, - std::vector<size_t> count_ = {}, - std::vector<size_t> stride_ = {}, - std::vector<size_t> block_ = {}) + RegularHyperSlab(const std::vector<size_t>& offset_, + const std::vector<size_t>& count_ = {}, + const std::vector<size_t>& stride_ = {}, + const std::vector<size_t>& block_ = {}) : offset(toHDF5SizeVector(offset_)) , count(toHDF5SizeVector(count_)) , stride(toHDF5SizeVector(stride_)) @@ -87,10 +77,10 @@ struct RegularHyperSlab { std::vector<hsize_t> stride_ = {}, std::vector<hsize_t> block_ = {}) { RegularHyperSlab slab; - slab.offset = offset_; - slab.count = count_; - slab.stride = stride_; - slab.block = block_; + slab.offset = std::move(offset_); + slab.count = std::move(count_); + slab.stride = std::move(stride_); + slab.block = std::move(block_); return slab; } @@ -368,6 +358,28 @@ class SliceTraits { /// template <typename T> void write_raw(const T* buffer, const DataTransferProps& xfer_props = DataTransferProps()); + + /// + /// \brief Return a `Selection` with `axes` squeezed from the memspace. + /// + /// Returns a selection in which the memspace has been modified + /// to not include the axes listed in `axes`. + /// + /// Throws if any axis to be squeezes has a dimension other than `1`. + /// + /// \since 3.0 + Selection squeezeMemSpace(const std::vector<size_t>& axes) const; + + /// + /// \brief Return a `Selection` with a simple memspace with `dims`. + /// + /// Returns a selection in which the memspace has been modified + /// to be a simple dataspace with dimensions `dims`. + /// + /// Throws if the number of elements changes. + /// + /// \since 3.0 + Selection reshapeMemSpace(const std::vector<size_t>& dims) const; }; } // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp b/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp index 2ae6640b0..711d57ac2 100644 --- a/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp +++ b/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp @@ -20,6 +20,9 @@ #include "H5ReadWrite_misc.hpp" #include "H5Converter_misc.hpp" +#include "squeeze.hpp" +#include "compute_total_size.hpp" +#include "assert_compatible_spaces.hpp" namespace HighFive { @@ -177,10 +180,11 @@ inline void SliceTraits<Derivate>::read(T& array, const DataTransferProps& xfer_ [&slice]() -> std::string { return details::get_dataset(slice).getPath(); }, details::BufferInfo<T>::Operation::read); - if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) { + if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) { std::ostringstream ss; ss << "Impossible to read DataSet of dimensions " << mem_space.getNumberDimensions() - << " into arrays of dimensions " << buffer_info.n_dimensions; + << " into arrays of dimensions: " << buffer_info.getMinRank() << "(min) to " + << buffer_info.getMaxRank() << "(max)"; throw DataSpaceException(ss.str()); } auto dims = mem_space.getDimensions(); @@ -251,11 +255,11 @@ inline void SliceTraits<Derivate>::write(const T& buffer, const DataTransferProp [&slice]() -> std::string { return details::get_dataset(slice).getPath(); }, details::BufferInfo<T>::Operation::write); - if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) { + if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) { std::ostringstream ss; - ss << "Impossible to write buffer of dimensions " - << details::format_vector(mem_space.getDimensions()) - << " into dataset with n = " << buffer_info.n_dimensions << " dimensions."; + ss << "Impossible to write buffer with dimensions n = " << buffer_info.getRank(buffer) + << "into dataset with dimensions " << details::format_vector(mem_space.getDimensions()) + << "."; throw DataSpaceException(ss.str()); } auto w = details::data_converter::serialize<T>(buffer, dims, file_datatype); @@ -288,5 +292,34 @@ inline void SliceTraits<Derivate>::write_raw(const T* buffer, const DataTransfer write_raw(buffer, mem_datatype, xfer_props); } +namespace detail { +inline const DataSet& getDataSet(const Selection& selection) { + return selection.getDataset(); +} + +inline const DataSet& getDataSet(const DataSet& dataset) { + return dataset; +} + +} // namespace detail + +template <typename Derivate> +inline Selection SliceTraits<Derivate>::squeezeMemSpace(const std::vector<size_t>& axes) const { + auto slice = static_cast<const Derivate&>(*this); + auto mem_dims = slice.getMemSpace().getDimensions(); + auto squeezed_dims = detail::squeeze(mem_dims, axes); + + return detail::make_selection(DataSpace(squeezed_dims), + slice.getSpace(), + detail::getDataSet(slice)); +} + +template <typename Derivate> +inline Selection SliceTraits<Derivate>::reshapeMemSpace(const std::vector<size_t>& new_dims) const { + auto slice = static_cast<const Derivate&>(*this); + + detail::assert_compatible_spaces(slice.getMemSpace(), new_dims); + return detail::make_selection(DataSpace(new_dims), slice.getSpace(), detail::getDataSet(slice)); +} } // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp b/packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp new file mode 100644 index 000000000..f4be279d4 --- /dev/null +++ b/packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (c), 2024, BlueBrain Project, EPFL + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ +#pragma once + +#include <vector> +#include "../H5Exception.hpp" +#include "../H5DataSpace.hpp" + +namespace HighFive { +namespace detail { + +inline void assert_compatible_spaces(const DataSpace& old, const std::vector<size_t>& dims) { + auto n_elements_old = old.getElementCount(); + auto n_elements_new = dims.size() == 0 ? 1 : compute_total_size(dims); + + if (n_elements_old != n_elements_new) { + throw Exception("Invalid parameter `new_dims` number of elements differ: " + + std::to_string(n_elements_old) + " (old) vs. " + + std::to_string(n_elements_new) + " (new)"); + } +} +} // namespace detail +} // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/compute_total_size.hpp b/packages/HighFive/include/highfive/bits/compute_total_size.hpp new file mode 100644 index 000000000..5be8a5999 --- /dev/null +++ b/packages/HighFive/include/highfive/bits/compute_total_size.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include <cstddef> +#include <numeric> +#include <functional> +#include <vector> + +namespace HighFive { + +inline size_t compute_total_size(const std::vector<size_t>& dims) { + return std::accumulate(dims.begin(), dims.end(), size_t{1u}, std::multiplies<size_t>()); +} + +} // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/convert_size_vector.hpp b/packages/HighFive/include/highfive/bits/convert_size_vector.hpp new file mode 100644 index 000000000..62a815ae5 --- /dev/null +++ b/packages/HighFive/include/highfive/bits/convert_size_vector.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c), 2017, Adrien Devresse <adrien.devresse@epfl.ch> + * Copyright (c), 2017-2024, BlueBrain Project, EPFL + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ +#pragma once + +#include <vector> + +namespace HighFive { +namespace detail { + +template <class To, class From, class It = From const*> +inline std::vector<To> convertSizeVector(const It& begin, const It& end) { + std::vector<To> to(static_cast<size_t>(end - begin)); + std::copy(begin, end, to.begin()); + + return to; +} + +template <class To, class From> +inline std::vector<To> convertSizeVector(const std::vector<From>& from) { + return convertSizeVector<To, From>(from.cbegin(), from.cend()); +} + +} // namespace detail +} // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp b/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp index 75b91bb6a..df97c3ca1 100644 --- a/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp +++ b/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp @@ -15,5 +15,14 @@ inline hid_t h5o_open(hid_t loc_id, const char* name, hid_t lapl_id) { return hid; } +inline herr_t h5o_close(hid_t id) { + herr_t err = H5Oclose(id); + if (err < 0) { + HDF5ErrMapper::ToException<ObjectException>("Unable to close object."); + } + + return err; +} + } // namespace detail } // namespace HighFive diff --git a/packages/HighFive/include/highfive/bits/squeeze.hpp b/packages/HighFive/include/highfive/bits/squeeze.hpp new file mode 100644 index 000000000..4be610e34 --- /dev/null +++ b/packages/HighFive/include/highfive/bits/squeeze.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (c), 2024, BlueBrain Project, EPFL + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ +#pragma once + +#include <vector> +#include "../H5Exception.hpp" + +namespace HighFive { +namespace detail { + +/// \brief Squeeze `axes` from `dims`. +/// +/// An axis can only be squeezed if it's dimension is `1`. The elements of +/// `axes` must be in the range `0, ..., dims.size()` (exclusive) and don't +/// have to be sorted. +/// +/// Example: +/// squeeze({1, 3, 2, 1}, {0, 3}) == {3, 2} +inline std::vector<size_t> squeeze(const std::vector<size_t>& dims, + const std::vector<size_t>& axes) { + auto n_dims = dims.size(); + auto mask = std::vector<bool>(n_dims, false); + for (size_t i = 0; i < axes.size(); ++i) { + if (axes[i] >= n_dims) { + throw Exception("Out of range: axes[" + std::to_string(i) + + "] == " + std::to_string(axes[i]) + " >= " + std::to_string(n_dims)); + } + + mask[axes[i]] = true; + } + + auto squeezed_dims = std::vector<size_t>{}; + for (size_t i = 0; i < n_dims; ++i) { + if (!mask[i]) { + squeezed_dims.push_back(dims[i]); + } else { + if (dims[i] != 1) { + throw Exception("Squeezing non-unity axis: axes[" + std::to_string(i) + + "] = " + std::to_string(axes[i])); + } + } + } + + return squeezed_dims; +} + +} // namespace detail +} // namespace HighFive diff --git a/packages/HighFive/include/highfive/boost.hpp b/packages/HighFive/include/highfive/boost.hpp index fb8a709c5..33c1458df 100644 --- a/packages/HighFive/include/highfive/boost.hpp +++ b/packages/HighFive/include/highfive/boost.hpp @@ -17,17 +17,31 @@ struct inspector<boost::multi_array<T, Dims>> { using hdf5_type = typename inspector<value_type>::hdf5_type; static constexpr size_t ndim = Dims; - static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim; + static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim; + static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value && - inspector<value_type>::is_trivially_copyable; + inspector<value_type>::is_trivially_nestable; + static constexpr bool is_trivially_nestable = false; + + + static size_t getRank(const type& val) { + return ndim + inspector<value_type>::getRank(val.data()[0]); + } static std::vector<size_t> getDimensions(const type& val) { - std::vector<size_t> sizes; + auto rank = getRank(val); + std::vector<size_t> sizes(rank, 1ul); for (size_t i = 0; i < ndim; ++i) { - sizes.push_back(val.shape()[i]); + sizes[i] = val.shape()[i]; + } + if (val.size() != 0) { + auto s = inspector<value_type>::getDimensions(val.data()[0]); + sizes.resize(ndim + s.size()); + for (size_t i = 0; i < s.size(); ++i) { + sizes[ndim + i] = s[i]; + } } - auto s = inspector<value_type>::getDimensions(val.data()[0]); - sizes.insert(sizes.end(), s.begin(), s.end()); return sizes; } @@ -51,16 +65,25 @@ struct inspector<boost::multi_array<T, Dims>> { } } + static void assert_c_order(const type& val) { + if (!(val.storage_order() == boost::c_storage_order())) { + throw DataTypeException("Only C storage order is supported for 'boost::multi_array'."); + } + } + static hdf5_type* data(type& val) { + assert_c_order(val); return inspector<value_type>::data(*val.data()); } static const hdf5_type* data(const type& val) { + assert_c_order(val); return inspector<value_type>::data(*val.data()); } template <class It> static void serialize(const type& val, const std::vector<size_t>& dims, It m) { + assert_c_order(val); size_t size = val.num_elements(); auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end()); size_t subsize = compute_total_size(subdims); @@ -71,6 +94,7 @@ struct inspector<boost::multi_array<T, Dims>> { template <class It> static void unserialize(It vec_align, const std::vector<size_t>& dims, type& val) { + assert_c_order(val); std::vector<size_t> next_dims(dims.begin() + ndim, dims.end()); size_t subsize = compute_total_size(next_dims); for (size_t i = 0; i < val.num_elements(); ++i) { @@ -89,9 +113,16 @@ struct inspector<boost::numeric::ublas::matrix<T>> { using hdf5_type = typename inspector<value_type>::hdf5_type; static constexpr size_t ndim = 2; - static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim; + static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim; + static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value && inspector<value_type>::is_trivially_copyable; + static constexpr bool is_trivially_nestable = false; + + static size_t getRank(const type& val) { + return ndim + inspector<value_type>::getRank(val(0, 0)); + } static std::vector<size_t> getDimensions(const type& val) { std::vector<size_t> sizes{val.size1(), val.size2()}; diff --git a/packages/HighFive/include/highfive/eigen.hpp b/packages/HighFive/include/highfive/eigen.hpp index aaad280ef..462769e4b 100644 --- a/packages/HighFive/include/highfive/eigen.hpp +++ b/packages/HighFive/include/highfive/eigen.hpp @@ -16,6 +16,7 @@ struct eigen_inspector { using base_type = typename inspector<value_type>::base_type; using hdf5_type = base_type; + static_assert(int(EigenType::ColsAtCompileTime) == int(EigenType::MaxColsAtCompileTime), "Padding isn't supported."); static_assert(int(EigenType::RowsAtCompileTime) == int(EigenType::MaxRowsAtCompileTime), @@ -26,11 +27,18 @@ struct eigen_inspector { EigenType::IsRowMajor; } + static constexpr size_t ndim = 2; - static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim; + static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim; static constexpr bool is_trivially_copyable = is_row_major() && std::is_trivially_copyable<value_type>::value && - inspector<value_type>::is_trivially_copyable; + inspector<value_type>::is_trivially_nestable; + static constexpr bool is_trivially_nestable = false; + + static size_t getRank(const type& val) { + return ndim + inspector<value_type>::getRank(val.data()[0]); + } static std::vector<size_t> getDimensions(const type& val) { std::vector<size_t> sizes{static_cast<size_t>(val.rows()), static_cast<size_t>(val.cols())}; diff --git a/packages/HighFive/include/highfive/experimental/opencv.hpp b/packages/HighFive/include/highfive/experimental/opencv.hpp new file mode 100644 index 000000000..224160975 --- /dev/null +++ b/packages/HighFive/include/highfive/experimental/opencv.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include "../bits/H5Inspector_decl.hpp" +#include "../H5Exception.hpp" + +#include <opencv2/opencv.hpp> + +#include "../bits/convert_size_vector.hpp" + +namespace HighFive { +namespace details { + + +template <class T> +struct inspector<cv::Mat_<T>> { + using type = cv::Mat_<T>; + using value_type = T; + using base_type = typename inspector<value_type>::base_type; + using hdf5_type = base_type; + + static void assert_row_major(const type& type) { + // Documentation claims that Mat_ is always row-major. However, it + // could be padded. The steps/strides are in bytes. + int rank = type.dims; + size_t ld = sizeof(T); + for (int i = rank - 1; i >= 0; --i) { + if (static_cast<size_t>(type.step[i]) != ld) { + throw DataSetException("Padded cv::Mat_ are not supported."); + } + + ld *= static_cast<size_t>(type.size[i]); + } + } + + + static constexpr size_t min_ndim = 2 + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = 1024 + inspector<value_type>::max_ndim; + + // HighFive doesn't support padded OpenCV arrays. Therefore, pretend + // that they themselves are trivially copyable. And error out if the + // assumption is violated. + static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value && + inspector<value_type>::is_trivially_nestable; + static constexpr bool is_trivially_nestable = false; + + static size_t getRank(const type& val) { + if (val.empty()) { + return min_ndim; + + } else { + return static_cast<size_t>(val.dims) + + inspector<value_type>::getRank(getAnyElement(val)); + } + } + + static const T& getAnyElement(const type& val) { + return *reinterpret_cast<T const*>(val.data); + } + + static T& getAnyElement(type& val) { + return *reinterpret_cast<T*>(val.data); + } + + static size_t getLocalRank(const type& val) { + return static_cast<size_t>(val.dims); + } + + static std::vector<size_t> getDimensions(const type& val) { + auto local_rank = getLocalRank(val); + auto rank = getRank(val); + std::vector<size_t> dims(rank, 1ul); + + if (val.empty()) { + dims[0] = 0ul; + dims[1] = 1ul; + return dims; + } + + for (size_t i = 0; i < local_rank; ++i) { + dims[i] = static_cast<size_t>(val.size[static_cast<int>(i)]); + } + + auto s = inspector<value_type>::getDimensions(getAnyElement(val)); + std::copy(s.cbegin(), s.cend(), dims.begin() + static_cast<int>(local_rank)); + return dims; + } + + static void prepare(type& val, const std::vector<size_t>& dims) { + auto subdims = detail::convertSizeVector<int>(dims); + val.create(static_cast<int>(subdims.size()), subdims.data()); + } + + static hdf5_type* data(type& val) { + assert_row_major(val); + + if (!is_trivially_copyable) { + throw DataSetException("Invalid used of `inspector<Eigen::Matrix<...>>::data`."); + } + + if (val.empty()) { + return nullptr; + } + + return inspector<value_type>::data(getAnyElement(val)); + } + + static const hdf5_type* data(const type& val) { + assert_row_major(val); + + if (!is_trivially_copyable) { + throw DataSetException("Invalid used of `inspector<Eigen::Matrix<...>>::data`."); + } + + if (val.empty()) { + return nullptr; + } + + return inspector<value_type>::data(getAnyElement(val)); + } + + static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) { + if (val.empty()) { + return; + } + + auto local_rank = val.dims; + auto subdims = std::vector<size_t>(dims.begin() + local_rank, dims.end()); + auto subsize = compute_total_size(subdims); + for (auto it = val.begin(); it != val.end(); ++it) { + inspector<value_type>::serialize(*it, subdims, m); + m += subsize; + } + } + + static void unserialize(const hdf5_type* vec_align, + const std::vector<size_t>& dims, + type& val) { + auto local_rank = val.dims; + auto subdims = std::vector<size_t>(dims.begin() + local_rank, dims.end()); + auto subsize = compute_total_size(subdims); + for (auto it = val.begin(); it != val.end(); ++it) { + inspector<value_type>::unserialize(vec_align, subdims, *it); + vec_align += subsize; + } + } +}; + +} // namespace details +} // namespace HighFive diff --git a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp index 9b737f03b..ba27bc84a 100644 --- a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp +++ b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp @@ -20,6 +20,12 @@ namespace detail { template <typename T> struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> { + inline static void assert_row_major(const File& file, const std::string& path, const T& data) { + if (data.layout() != xt::layout_type::row_major) { + throw detail::error(file, path, "Only row-major XTensor object are supported."); + } + } + inline static std::vector<size_t> shape(const T& data) { return std::vector<size_t>(data.shape().cbegin(), data.shape().cend()); } @@ -28,6 +34,7 @@ struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> { const std::string& path, const T& data, const DumpOptions& options) { + assert_row_major(file, path, data); using value_type = typename std::decay_t<T>::value_type; DataSet dataset = initDataset<value_type>(file, path, shape(data), options); dataset.write_raw(data.data()); @@ -44,6 +51,7 @@ struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> { DataSet dataset = file.getDataSet(path); std::vector<size_t> dims = dataset.getDimensions(); T data = T::from_shape(dims); + assert_row_major(file, path, data); dataset.read_raw(data.data()); return data; } @@ -53,6 +61,7 @@ struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> { const std::string& key, const T& data, const DumpOptions& options) { + assert_row_major(file, path, data); using value_type = typename std::decay_t<T>::value_type; Attribute attribute = initAttribute<value_type>(file, path, key, shape(data), options); attribute.write_raw(data.data()); @@ -73,6 +82,7 @@ struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> { DataSpace dataspace = attribute.getSpace(); std::vector<size_t> dims = dataspace.getDimensions(); T data = T::from_shape(dims); + assert_row_major(file, path, data); attribute.read_raw(data.data()); return data; } diff --git a/packages/HighFive/include/highfive/span.hpp b/packages/HighFive/include/highfive/span.hpp new file mode 100644 index 000000000..ab53319ee --- /dev/null +++ b/packages/HighFive/include/highfive/span.hpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 Blue Brain Project + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ + +#pragma once + +#include "bits/H5Inspector_decl.hpp" +#include "H5Exception.hpp" + +#include <span> + +namespace HighFive { +namespace details { + +template <class T, std::size_t Extent> +struct inspector<std::span<T, Extent>> { + using type = std::span<T, Extent>; + using value_type = unqualified_t<T>; + using base_type = typename inspector<value_type>::base_type; + using hdf5_type = typename inspector<value_type>::hdf5_type; + + static constexpr size_t ndim = 1; + static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim; + + static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value && + inspector<value_type>::is_trivially_nestable; + static constexpr bool is_trivially_nestable = false; + + + static size_t getRank(const type& val) { + if (!val.empty()) { + return ndim + inspector<value_type>::getRank(val[0]); + } else { + return min_ndim; + } + } + + static std::vector<size_t> getDimensions(const type& val) { + auto rank = getRank(val); + std::vector<size_t> sizes(rank, 1ul); + sizes[0] = val.size(); + if (!val.empty()) { + auto s = inspector<value_type>::getDimensions(val[0]); + assert(s.size() + ndim == sizes.size()); + for (size_t i = 0; i < s.size(); ++i) { + sizes[i + ndim] = s[i]; + } + } + return sizes; + } + + static void prepare(type& val, const std::vector<size_t>& expected_dims) { + auto actual_dims = getDimensions(val); + if (actual_dims.size() != expected_dims.size()) { + throw DataSpaceException("Mismatching rank."); + } + + for (size_t i = 0; i < actual_dims.size(); ++i) { + if (actual_dims[i] != expected_dims[i]) { + throw DataSpaceException("Mismatching dimensions."); + } + } + } + + static hdf5_type* data(type& val) { + return val.empty() ? nullptr : inspector<value_type>::data(val[0]); + } + + static const hdf5_type* data(const type& val) { + return val.empty() ? nullptr : inspector<value_type>::data(val[0]); + } + + template <class It> + static void serialize(const type& val, const std::vector<size_t>& dims, It m) { + if (!val.empty()) { + auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end()); + size_t subsize = compute_total_size(subdims); + for (const auto& e: val) { + inspector<value_type>::serialize(e, subdims, m); + m += subsize; + } + } + } + + template <class It> + static void unserialize(const It& vec_align, const std::vector<size_t>& dims, type& val) { + std::vector<size_t> subdims(dims.begin() + ndim, dims.end()); + size_t subsize = compute_total_size(subdims); + for (size_t i = 0; i < dims[0]; ++i) { + inspector<value_type>::unserialize(vec_align + i * subsize, subdims, val[i]); + } + } +}; + +} // namespace details +} // namespace HighFive diff --git a/packages/HighFive/include/highfive/xtensor.hpp b/packages/HighFive/include/highfive/xtensor.hpp new file mode 100644 index 000000000..729a1e349 --- /dev/null +++ b/packages/HighFive/include/highfive/xtensor.hpp @@ -0,0 +1,212 @@ +#pragma once + +#include "bits/H5Inspector_decl.hpp" +#include "H5Exception.hpp" + +#include <xtensor/xtensor.hpp> +#include <xtensor/xarray.hpp> +#include <xtensor/xadapt.hpp> + +namespace HighFive { +namespace details { + +template <class XTensor> +struct xtensor_get_rank; + +template <typename T, size_t N, xt::layout_type L> +struct xtensor_get_rank<xt::xtensor<T, N, L>> { + static constexpr size_t value = N; +}; + +template <class EC, size_t N, xt::layout_type L, class Tag> +struct xtensor_get_rank<xt::xtensor_adaptor<EC, N, L, Tag>> { + static constexpr size_t value = N; +}; + +template <class Derived, class XTensorType, xt::layout_type L> +struct xtensor_inspector_base { + using type = XTensorType; + using value_type = typename type::value_type; + using base_type = typename inspector<value_type>::base_type; + using hdf5_type = base_type; + + static_assert(std::is_same<value_type, base_type>::value, + "HighFive's XTensor support only works for scalar elements."); + + static constexpr bool IsConstExprRowMajor = L == xt::layout_type::row_major; + static constexpr bool is_trivially_copyable = IsConstExprRowMajor && + std::is_trivially_copyable<value_type>::value && + inspector<value_type>::is_trivially_copyable; + + static constexpr bool is_trivially_nestable = false; + + static size_t getRank(const type& val) { + // Non-scalar elements are not supported. + return val.shape().size(); + } + + static const value_type& getAnyElement(const type& val) { + return val.unchecked(0); + } + + static value_type& getAnyElement(type& val) { + return val.unchecked(0); + } + + static std::vector<size_t> getDimensions(const type& val) { + auto shape = val.shape(); + return {shape.begin(), shape.end()}; + } + + static void prepare(type& val, const std::vector<size_t>& dims) { + val.resize(Derived::shapeFromDims(dims)); + } + + static hdf5_type* data(type& val) { + if (!is_trivially_copyable) { + throw DataSetException("Invalid used of `inspector<XTensor>::data`."); + } + + if (val.size() == 0) { + return nullptr; + } + + return inspector<value_type>::data(getAnyElement(val)); + } + + static const hdf5_type* data(const type& val) { + if (!is_trivially_copyable) { + throw DataSetException("Invalid used of `inspector<XTensor>::data`."); + } + + if (val.size() == 0) { + return nullptr; + } + + return inspector<value_type>::data(getAnyElement(val)); + } + + static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) { + // since we only support scalar types we know all dims belong to us. + size_t size = compute_total_size(dims); + xt::adapt(m, size, xt::no_ownership(), dims) = val; + } + + static void unserialize(const hdf5_type* vec_align, + const std::vector<size_t>& dims, + type& val) { + // since we only support scalar types we know all dims belong to us. + size_t size = compute_total_size(dims); + val = xt::adapt(vec_align, size, xt::no_ownership(), dims); + } +}; + +template <class XTensorType, xt::layout_type L> +struct xtensor_inspector + : public xtensor_inspector_base<xtensor_inspector<XTensorType, L>, XTensorType, L> { + private: + using super = xtensor_inspector_base<xtensor_inspector<XTensorType, L>, XTensorType, L>; + + public: + using type = typename super::type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; + using hdf5_type = typename super::hdf5_type; + + static constexpr size_t ndim = xtensor_get_rank<XTensorType>::value; + static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim; + + static std::array<size_t, ndim> shapeFromDims(const std::vector<size_t>& dims) { + std::array<size_t, ndim> shape; + std::copy(dims.cbegin(), dims.cend(), shape.begin()); + return shape; + } +}; + +template <class XArrayType, xt::layout_type L> +struct xarray_inspector + : public xtensor_inspector_base<xarray_inspector<XArrayType, L>, XArrayType, L> { + private: + using super = xtensor_inspector_base<xarray_inspector<XArrayType, L>, XArrayType, L>; + + public: + using type = typename super::type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; + using hdf5_type = typename super::hdf5_type; + + static constexpr size_t min_ndim = 0 + inspector<value_type>::min_ndim; + static constexpr size_t max_ndim = 1024 + inspector<value_type>::max_ndim; + + static const std::vector<size_t>& shapeFromDims(const std::vector<size_t>& dims) { + return dims; + } +}; + +template <typename T, size_t N, xt::layout_type L> +struct inspector<xt::xtensor<T, N, L>>: public xtensor_inspector<xt::xtensor<T, N, L>, L> { + private: + using super = xtensor_inspector<xt::xtensor<T, N, L>, L>; + + public: + using type = typename super::type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; + using hdf5_type = typename super::hdf5_type; +}; + +template <typename T, xt::layout_type L> +struct inspector<xt::xarray<T, L>>: public xarray_inspector<xt::xarray<T, L>, L> { + private: + using super = xarray_inspector<xt::xarray<T, L>, L>; + + public: + using type = typename super::type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; + using hdf5_type = typename super::hdf5_type; +}; + +template <typename CT, class... S> +struct inspector<xt::xview<CT, S...>> + : public xarray_inspector<xt::xview<CT, S...>, xt::layout_type::any> { + private: + using super = xarray_inspector<xt::xview<CT, S...>, xt::layout_type::any>; + + public: + using type = typename super::type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; + using hdf5_type = typename super::hdf5_type; +}; + + +template <class EC, xt::layout_type L, class SC, class Tag> +struct inspector<xt::xarray_adaptor<EC, L, SC, Tag>> + : public xarray_inspector<xt::xarray_adaptor<EC, L, SC, Tag>, xt::layout_type::any> { + private: + using super = xarray_inspector<xt::xarray_adaptor<EC, L, SC, Tag>, xt::layout_type::any>; + + public: + using type = typename super::type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; + using hdf5_type = typename super::hdf5_type; +}; + +template <class EC, size_t N, xt::layout_type L, class Tag> +struct inspector<xt::xtensor_adaptor<EC, N, L, Tag>> + : public xtensor_inspector<xt::xtensor_adaptor<EC, N, L, Tag>, xt::layout_type::any> { + private: + using super = xtensor_inspector<xt::xtensor_adaptor<EC, N, L, Tag>, xt::layout_type::any>; + + public: + using type = typename super::type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; + using hdf5_type = typename super::hdf5_type; +}; + +} // namespace details +} // namespace HighFive diff --git a/packages/HighFive/src/examples/CMakeLists.txt b/packages/HighFive/src/examples/CMakeLists.txt index 5a1384557..5f4b4edf8 100644 --- a/packages/HighFive/src/examples/CMakeLists.txt +++ b/packages/HighFive/src/examples/CMakeLists.txt @@ -1,5 +1,6 @@ set(core_examples ${CMAKE_CURRENT_SOURCE_DIR}/compound_types.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/broadcasting_arrays.cpp ${CMAKE_CURRENT_SOURCE_DIR}/create_attribute_string_integer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/create_dataset_double.cpp ${CMAKE_CURRENT_SOURCE_DIR}/create_datatype.cpp @@ -18,6 +19,10 @@ set(core_examples ${CMAKE_CURRENT_SOURCE_DIR}/select_partial_dataset_cpp11.cpp ) +set(span_examples + ${CMAKE_CURRENT_SOURCE_DIR}/read_write_std_span.cpp +) + set(easy_examples ${CMAKE_CURRENT_SOURCE_DIR}/easy_attribute.cpp ${CMAKE_CURRENT_SOURCE_DIR}/easy_dumpoptions.cpp @@ -52,7 +57,6 @@ set(half_float_examples function(compile_example example_source) get_filename_component(example_filename ${example_source} NAME) string(REPLACE ".cpp" "_bin" example_name ${example_filename}) - message("example_name: ${example_name}") add_executable(${example_name} ${example_source}) target_link_libraries(${example_name} PUBLIC HighFive HighFiveWarnings HighFiveFlags) @@ -70,6 +74,12 @@ foreach(example_source ${easy_examples}) compile_example(${example_source}) endforeach() +if(HIGHFIVE_TEST_SPAN) + foreach(example_source ${span_examples}) + compile_example(${example_source}) + endforeach() +endif() + if(HIGHFIVE_TEST_BOOST) foreach(example_source ${boost_examples}) compile_example(${example_source} HighFiveBoostDependency) diff --git a/packages/HighFive/src/examples/broadcasting_arrays.cpp b/packages/HighFive/src/examples/broadcasting_arrays.cpp new file mode 100644 index 000000000..3684e17b0 --- /dev/null +++ b/packages/HighFive/src/examples/broadcasting_arrays.cpp @@ -0,0 +1,50 @@ +#include <highfive/highfive.hpp> + +// This example explains how to read a dataset with some shape into an array of +// some other shape. Naturally, this only makes sense if the number of elements +// doesn't change. +// +// Note that due to how HDF5 works, writing from one shape into some other +// shape is expected to work automatically. +// +// Same is true for reading. However, HighFive also allocates memory, the array +// into which the data is read is forced to have the same shape as the +// memspace. When performing selections it can often happen that one selects a +// one-dimensional slice from a higher dimensional array. In this case we want +// to be able to read into a one dimensional array, e.g. `std::vector<double>`. +// +// Broadcasting is a common technique for hiding benign differences in +// dimensionality. In HighFive we suggest to either "squeeze" or "reshape" the +// memspace, rather than broadcasting. This example demonstrates the required +// syntax. +// +// Note: These techniques can also be used for general hyperslabs which the +// user knows are in fact hypercubes, i.e. regular. +// +// Note: HighFive v2 has support for broadcasting; but because it's quirky, +// less powerful than the demonstrated technique, relied on a compile-time +// constant rank and is quite complex to maintain, the functionality was +// removed from v3. + +using namespace HighFive; + +int main(void) { + File file("broadcasting_arrays.h5", File::Truncate); + + std::vector<size_t> dims{3, 1}; + std::vector<double> values{1.0, 2.0, 3.0}; + + auto dset = file.createDataSet("dset", DataSpace(dims), create_datatype<double>()); + + // Note that because `values` is one-dimensional, we can't write it + // to a dataset of dimensions `[3, 1]` directly. Instead we use: + dset.squeezeMemSpace({1}).write(values); + + // When reading, (re-)allocation might occur. The shape to be allocated is + // the dimensions of the memspace. Therefore, one might want to either remove + // an axis: + dset.squeezeMemSpace({1}).read(values); + + // or reshape the memspace: + dset.reshapeMemSpace({3}).read(values); +} diff --git a/packages/HighFive/src/examples/read_write_std_span.cpp b/packages/HighFive/src/examples/read_write_std_span.cpp new file mode 100644 index 000000000..72465c46d --- /dev/null +++ b/packages/HighFive/src/examples/read_write_std_span.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c), 2024, Blue Brain Project + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ + +// This example demonstrates using `std::span`. An `std::span` is a pointer +// with a size. + +#include <string> +#include <vector> + +#include <highfive/highfive.hpp> + +#include <highfive/span.hpp> + +int main(void) { + using namespace HighFive; + + std::string file_name = "read_write_span.h5"; + std::string dataset_name = "array"; + + File file(file_name, File::Truncate); + + // Let's write to file. + { + // Assume we have one-dimensional data in some unsupported format (we + // use `std::vector` for simplicity). Further, assume that the data is + // stored contiguously. Then one can create an `std::span`. + std::vector<double> values{1.0, 2.0, 3.0}; + auto view = std::span<double>(values.data(), values.size()); + + // Given the span, HighFive can deduce the shape of the dataset. Hence, + // spans are fully supported when writing. For example: + auto dataset = file.createDataSet(dataset_name, view); + } + + // Let's read from file. + { + auto dataset = file.getDataSet(dataset_name); + + // Since spans are views, HighFive can't (or wont) allocate memory. + // Instead one must preallocate memory and then create a span for that + // memory: + auto values = std::vector<double>(dataset.getElementCount()); + auto view = std::span<double>(values.data(), values.size()); + + // ... now we can read into the preallocated memory: + dataset.read(view); + } + + return 0; +} diff --git a/packages/HighFive/tests/unit/CMakeLists.txt b/packages/HighFive/tests/unit/CMakeLists.txt index c5a07e8e8..c8835ba34 100644 --- a/packages/HighFive/tests/unit/CMakeLists.txt +++ b/packages/HighFive/tests/unit/CMakeLists.txt @@ -6,7 +6,7 @@ if(MSVC) endif() ## Base tests -foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type test_legacy) +foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type test_empty_arrays test_legacy test_opencv test_string test_xtensor) add_executable(${test_name} "${test_name}.cpp") target_link_libraries(${test_name} HighFive HighFiveWarnings HighFiveFlags Catch2::Catch2WithMain) target_link_libraries(${test_name} HighFiveOptionalDependencies) @@ -14,7 +14,7 @@ foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_fiv catch_discover_tests(${test_name}) endforeach() -if(HIGHFIVE_PARALLEL_HDF5) +if(HDF5_IS_PARALLEL) set(tests_parallel_src "tests_high_five_parallel.cpp") ## parallel MPI tests @@ -42,26 +42,43 @@ if(HIGHFIVE_PARALLEL_HDF5) set(_CATCH_DISCOVER_TESTS_SCRIPT "${original_catch_script}") endif() -option(HIGHFIVE_TEST_SINGLE_INCLUDES "Enable testing single includes" FALSE) +# Test that each public header is self-sufficient. This is done by +# creating a file for each header, that only includes the header. The +# test succeeds if it compiles. +file(GLOB public_headers LIST_DIRECTORIES false RELATIVE ${PROJECT_SOURCE_DIR}/include CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/include/highfive/*.hpp) +foreach(PUBLIC_HEADER ${public_headers}) + if(PUBLIC_HEADER STREQUAL "highfive/span.hpp" AND NOT HIGHFIVE_TEST_SPAN) + continue() + endif() -if(HIGHFIVE_TEST_SINGLE_INCLUDES) - file(GLOB CONFIGURE_DEPENDS public_headers LIST_DIRECTORIES false RELATIVE ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include/highfive/*.hpp) - foreach(PUBLIC_HEADER ${public_headers}) - if(PUBLIC_HEADER STREQUAL "highfive/boost.hpp" AND NOT HIGHFIVE_TEST_BOOST) - continue() - endif() + if(PUBLIC_HEADER STREQUAL "highfive/boost.hpp" AND NOT HIGHFIVE_TEST_BOOST) + continue() + endif() - if(PUBLIC_HEADER STREQUAL "highfive/half_float.hpp" AND NOT HIGHFIVE_TEST_HALF_FLOAT) - continue() - endif() + if(PUBLIC_HEADER STREQUAL "highfive/half_float.hpp" AND NOT HIGHFIVE_TEST_HALF_FLOAT) + continue() + endif() - if(PUBLIC_HEADER STREQUAL "highfive/eigen.hpp" AND NOT HIGHFIVE_TEST_EIGEN) - continue() - endif() + if(PUBLIC_HEADER STREQUAL "highfive/eigen.hpp" AND NOT HIGHFIVE_TEST_EIGEN) + continue() + endif() - get_filename_component(CLASS_NAME ${PUBLIC_HEADER} NAME_WE) - configure_file(tests_import_public_headers.cpp "tests_${CLASS_NAME}.cpp" @ONLY) - add_executable("tests_include_${CLASS_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/tests_${CLASS_NAME}.cpp") - target_link_libraries("tests_include_${CLASS_NAME}" HighFive HighFiveWarnings HighFiveFlags) - endforeach() -endif() + if(PUBLIC_HEADER STREQUAL "highfive/opencv.hpp" AND NOT HIGHFIVE_TEST_OPENCV) + continue() + endif() + + if(PUBLIC_HEADER STREQUAL "highfive/xtensor.hpp" AND NOT HIGHFIVE_TEST_XTENSOR) + continue() + endif() + + get_filename_component(CLASS_NAME ${PUBLIC_HEADER} NAME_WE) + configure_file(tests_import_public_headers.cpp "tests_${CLASS_NAME}.cpp" @ONLY) + add_executable("tests_include_${CLASS_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/tests_${CLASS_NAME}.cpp") + target_link_libraries( + "tests_include_${CLASS_NAME}" PUBLIC + HighFive + HighFiveWarnings + HighFiveFlags + HighFiveOptionalDependencies + ) +endforeach() diff --git a/packages/HighFive/tests/unit/data_generator.hpp b/packages/HighFive/tests/unit/data_generator.hpp index 2964bf9fd..d513c3420 100644 --- a/packages/HighFive/tests/unit/data_generator.hpp +++ b/packages/HighFive/tests/unit/data_generator.hpp @@ -17,11 +17,20 @@ #include <highfive/eigen.hpp> #endif +#ifdef HIGHFIVE_TEST_SPAN +#include <highfive/span.hpp> +#endif + +#ifdef HIGHFIVE_TEST_XTENSOR +#include <highfive/xtensor.hpp> +#endif + namespace HighFive { namespace testing { -std::vector<size_t> lstrip(const std::vector<size_t>& indices, size_t n) { +template <class Dims> +std::vector<size_t> lstrip(const Dims& indices, size_t n) { std::vector<size_t> subindices(indices.size() - n); for (size_t i = 0; i < subindices.size(); ++i) { subindices[i] = indices[i + n]; @@ -30,7 +39,8 @@ std::vector<size_t> lstrip(const std::vector<size_t>& indices, size_t n) { return subindices; } -size_t ravel(std::vector<size_t>& indices, const std::vector<size_t> dims) { +template <class Dims> +size_t ravel(std::vector<size_t>& indices, const Dims& dims) { size_t rank = dims.size(); size_t linear_index = 0; size_t ld = 1; @@ -43,7 +53,8 @@ size_t ravel(std::vector<size_t>& indices, const std::vector<size_t> dims) { return linear_index; } -std::vector<size_t> unravel(size_t flat_index, const std::vector<size_t> dims) { +template <class Dims> +std::vector<size_t> unravel(size_t flat_index, const Dims& dims) { size_t rank = dims.size(); size_t ld = 1; std::vector<size_t> indices(rank); @@ -56,7 +67,8 @@ std::vector<size_t> unravel(size_t flat_index, const std::vector<size_t> dims) { return indices; } -static size_t flat_size(const std::vector<size_t>& dims) { +template <class Dims> +static size_t flat_size(const Dims& dims) { size_t n = 1; for (auto d: dims) { n *= d; @@ -75,6 +87,7 @@ struct ScalarContainerTraits { using base_type = T; static constexpr bool is_view = false; + static constexpr size_t rank = 0; static void set(container_type& array, std::vector<size_t> /* indices */, base_type value) { array = value; @@ -116,6 +129,7 @@ struct ContainerTraits<std::vector<bool>> { using base_type = bool; static constexpr bool is_view = false; + static constexpr size_t rank = 1; static void set(container_type& array, const std::vector<size_t>& indices, @@ -150,6 +164,7 @@ struct STLLikeContainerTraits { using base_type = typename ContainerTraits<value_type>::base_type; static constexpr bool is_view = ContainerTraits<value_type>::is_view; + static constexpr size_t rank = 1 + ContainerTraits<value_type>::rank; static void set(container_type& array, const std::vector<size_t>& indices, @@ -221,6 +236,53 @@ struct ContainerTraits<std::array<T, N>>: public STLLikeContainerTraits<std::arr } }; + +#ifdef HIGHFIVE_TEST_SPAN +template <class T, std::size_t Extent> +struct ContainerTraits<std::span<T, Extent>>: public STLLikeContainerTraits<std::span<T, Extent>> { + private: + using super = STLLikeContainerTraits<std::span<T, Extent>>; + + public: + using container_type = typename super::container_type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; + + static constexpr bool is_view = true; + + static container_type allocate(const std::vector<size_t>& dims) { + size_t n_elements = dims[0]; + value_type* ptr = new value_type[n_elements]; + + container_type array = container_type(ptr, n_elements); + + for (size_t i = 0; i < n_elements; ++i) { + auto element = ContainerTraits<value_type>::allocate(lstrip(dims, 1)); + ContainerTraits<value_type>::assign(array[i], element); + } + + return array; + } + + static void deallocate(container_type& array, const std::vector<size_t>& dims) { + size_t n_elements = dims[0]; + for (size_t i = 0; i < n_elements; ++i) { + ContainerTraits<value_type>::deallocate(array[i], lstrip(dims, 1)); + } + + delete[] array.data(); + } + + static void sanitize_dims(std::vector<size_t>& dims, size_t axis) { + if (Extent != std::dynamic_extent) { + dims[axis] = Extent; + ContainerTraits<value_type>::sanitize_dims(dims, axis + 1); + } + } +}; +#endif + + // -- Boost ------------------------------------------------------------------- #ifdef HIGHFIVE_TEST_BOOST template <class T, size_t n> @@ -230,6 +292,7 @@ struct ContainerTraits<boost::multi_array<T, n>> { using base_type = typename ContainerTraits<value_type>::base_type; static constexpr bool is_view = ContainerTraits<value_type>::is_view; + static constexpr size_t rank = n + ContainerTraits<value_type>::rank; static void set(container_type& array, const std::vector<size_t>& indices, @@ -282,6 +345,7 @@ struct ContainerTraits<boost::numeric::ublas::matrix<T>> { using base_type = typename ContainerTraits<value_type>::base_type; static constexpr bool is_view = ContainerTraits<value_type>::is_view; + static constexpr size_t rank = 2 + ContainerTraits<value_type>::rank; static void set(container_type& array, const std::vector<size_t>& indices, @@ -332,6 +396,7 @@ struct ContainerTraits<boost::numeric::ublas::matrix<T>> { #endif +// -- Eigen ------------------------------------------------------------------- #if HIGHFIVE_TEST_EIGEN template <typename EigenType> @@ -341,6 +406,7 @@ struct EigenContainerTraits { using base_type = typename ContainerTraits<value_type>::base_type; static constexpr bool is_view = ContainerTraits<value_type>::is_view; + static constexpr size_t rank = 2 + ContainerTraits<value_type>::rank; static void set(container_type& array, const std::vector<size_t>& indices, @@ -468,6 +534,88 @@ struct ContainerTraits<Eigen::Map<PlainObjectType, MapOptions>> }; +#endif + +// -- XTensor ----------------------------------------------------------------- + +#if HIGHFIVE_TEST_XTENSOR +template <typename XTensorType, size_t Rank> +struct XTensorContainerTraits { + using container_type = XTensorType; + using value_type = typename container_type::value_type; + using base_type = typename ContainerTraits<value_type>::base_type; + + static constexpr size_t rank = Rank; + static constexpr bool is_view = ContainerTraits<value_type>::is_view; + + static void set(container_type& array, + const std::vector<size_t>& indices, + const base_type& value) { + std::vector<size_t> local_indices(indices.begin(), indices.begin() + rank); + return ContainerTraits<value_type>::set(array[local_indices], lstrip(indices, rank), value); + } + + static base_type get(const container_type& array, const std::vector<size_t>& indices) { + std::vector<size_t> local_indices(indices.begin(), indices.begin() + rank); + return ContainerTraits<value_type>::get(array[local_indices], lstrip(indices, rank)); + } + + static void assign(container_type& dst, const container_type& src) { + dst = src; + } + + static container_type allocate(const std::vector<size_t>& dims) { + const auto& local_dims = details::inspector<XTensorType>::shapeFromDims(dims); + auto array = container_type(local_dims); + + size_t n_elements = flat_size(local_dims); + for (size_t i = 0; i < n_elements; ++i) { + auto element = ContainerTraits<value_type>::allocate(lstrip(dims, rank)); + set(array, unravel(i, local_dims), element); + } + + return array; + } + + static void deallocate(container_type& array, const std::vector<size_t>& dims) { + auto local_dims = std::vector<size_t>(dims.begin(), dims.begin() + rank); + size_t n_elements = flat_size(local_dims); + for (size_t i_flat = 0; i_flat < n_elements; ++i_flat) { + auto indices = unravel(i_flat, local_dims); + std::vector<size_t> local_indices(indices.begin(), indices.begin() + rank); + ContainerTraits<value_type>::deallocate(array[local_indices], lstrip(dims, rank)); + } + } + + static void sanitize_dims(std::vector<size_t>& dims, size_t axis) { + ContainerTraits<value_type>::sanitize_dims(dims, axis + rank); + } +}; + +template <class T, size_t rank, xt::layout_type layout> +struct ContainerTraits<xt::xtensor<T, rank, layout>> + : public XTensorContainerTraits<xt::xtensor<T, rank, layout>, rank> { + private: + using super = XTensorContainerTraits<xt::xtensor<T, rank, layout>, rank>; + + public: + using container_type = typename super::container_type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; +}; + +template <class T, xt::layout_type layout> +struct ContainerTraits<xt::xarray<T, layout>> + : public XTensorContainerTraits<xt::xarray<T, layout>, 2> { + private: + using super = XTensorContainerTraits<xt::xarray<T, layout>, 2>; + + public: + using container_type = typename super::container_type; + using value_type = typename super::value_type; + using base_type = typename super::base_type; +}; + #endif template <class T, class C> @@ -578,11 +726,12 @@ struct MultiDimVector<T, 0> { template <class Container> class DataGenerator { public: - constexpr static size_t rank = details::inspector<Container>::recursive_ndim; using traits = ContainerTraits<Container>; using base_type = typename traits::base_type; using container_type = Container; + constexpr static size_t rank = traits::rank; + public: static container_type allocate(const std::vector<size_t>& dims) { return traits::allocate(dims); diff --git a/packages/HighFive/tests/unit/supported_types.hpp b/packages/HighFive/tests/unit/supported_types.hpp index 75e442c60..5f1bf6453 100644 --- a/packages/HighFive/tests/unit/supported_types.hpp +++ b/packages/HighFive/tests/unit/supported_types.hpp @@ -34,6 +34,14 @@ struct STDArray { using type = std::array<typename C::template type<T>, n>; }; +#ifdef HIGHFIVE_TEST_SPAN +template <class C = type_identity> +struct STDSpan { + template <class T> + using type = std::span<typename C::template type<T>>; +}; +#endif + #ifdef HIGHFIVE_TEST_BOOST template <size_t n, class C = type_identity> struct BoostMultiArray { @@ -74,6 +82,20 @@ struct EigenMapMatrix { }; #endif +#ifdef HIGHFIVE_TEST_XTENSOR +template <size_t rank, xt::layout_type layout, class C = type_identity> +struct XTensor { + template <class T> + using type = xt::xtensor<typename C::template type<T>, rank, layout>; +}; + +template <xt::layout_type layout, class C = type_identity> +struct XArray { + template <class T> + using type = xt::xarray<typename C::template type<T>, layout>; +}; +#endif + template <class C, class Tuple> struct ContainerProduct; @@ -150,6 +172,23 @@ using supported_array_types = typename ConcatenateTuples< typename ContainerProduct<STDArray<7, EigenMatrix<3, 5, Eigen::RowMajor>>, scalar_types_eigen>::type, typename ContainerProduct<STDArray<7, EigenArray<Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>, scalar_types_eigen>::type, std::tuple<std::array<Eigen::VectorXd, 7>>, +#endif +#ifdef HIGHFIVE_TEST_SPAN + typename ContainerProduct<STDSpan<>, all_scalar_types>::type, + typename ContainerProduct<STDVector<STDSpan<>>, some_scalar_types>::type, + typename ContainerProduct<STDArray<7, STDSpan<>>, some_scalar_types>::type, + typename ContainerProduct<STDSpan<STDVector<>>, some_scalar_types>::type, + typename ContainerProduct<STDSpan<STDArray<3>>, some_scalar_types>::type, +#endif +#ifdef HIGHFIVE_TEST_XTENSOR + typename ContainerProduct<XTensor<3, xt::layout_type::row_major>, scalar_types_eigen>::type, + typename ContainerProduct<STDVector<XTensor<3, xt::layout_type::row_major>>, scalar_types_eigen>::type, + typename ContainerProduct<STDArray<5, XTensor<3, xt::layout_type::row_major>>, scalar_types_eigen>::type, + typename ContainerProduct<XTensor<3, xt::layout_type::column_major>, scalar_types_eigen>::type, + typename ContainerProduct<XArray<xt::layout_type::row_major>, scalar_types_eigen>::type, + typename ContainerProduct<STDVector<XArray<xt::layout_type::row_major>>, scalar_types_eigen>::type, + typename ContainerProduct<STDArray<5, XArray<xt::layout_type::row_major>>, scalar_types_eigen>::type, + typename ContainerProduct<XArray<xt::layout_type::column_major>, scalar_types_eigen>::type, #endif typename ContainerProduct<STDVector<>, all_scalar_types>::type, typename ContainerProduct<STDVector<STDVector<>>, some_scalar_types>::type, diff --git a/packages/HighFive/tests/unit/test_empty_arrays.cpp b/packages/HighFive/tests/unit/test_empty_arrays.cpp new file mode 100644 index 000000000..ea447ffaf --- /dev/null +++ b/packages/HighFive/tests/unit/test_empty_arrays.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c), 2017-2024, Blue Brain Project - EPFL + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ +#include <string> +#include <vector> + +#include <catch2/catch_test_macros.hpp> +#include <catch2/catch_template_test_macros.hpp> +#include <catch2/matchers/catch_matchers_vector.hpp> + +#include <highfive/highfive.hpp> +#include "tests_high_five.hpp" +#include "create_traits.hpp" + +#ifdef HIGHFIVE_TEST_BOOST +#include <highfive/boost.hpp> +#endif + +#ifdef HIGHFIVE_TEST_EIGEN +#include <highfive/eigen.hpp> +#endif + +#ifdef HIGHFIVE_TEST_SPAN +#include <highfive/span.hpp> +#endif + +using namespace HighFive; +using Catch::Matchers::Equals; + + +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 HIGHFIVE_TEST_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 HIGHFIVE_TEST_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); +} + +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).reshapeMemSpace({0ul}).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).reshapeMemSpace(dims).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).reshapeMemSpace(dims).read(output_data); + + check_empty_dimensions(output_data, dims); + } + + SECTION("read; auto-allocated") { + auto output_data = ReadWriteInterface::get(file, dataset_name) + .reshapeMemSpace(dims) + .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<testing::DataSetCreateTraits, CreateContainer>(dims); +} + +template <class CreateContainer> +void check_empty_attribute(const std::vector<size_t>& dims) { + check_empty_read_write_cycle<testing::AttributeCreateTraits, 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 HIGHFIVE_TEST_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 HIGHFIVE_TEST_BOOST + SECTION("boost::multi_array") { + check_empty_everything<CreateEmptyBoostMultiArray<ndim>>(dims); + } +#endif + +#ifdef HIGHFIVE_TEST_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); + } + } + } +} diff --git a/packages/HighFive/tests/unit/test_legacy.cpp b/packages/HighFive/tests/unit/test_legacy.cpp index 7d7e67f26..698131473 100644 --- a/packages/HighFive/tests/unit/test_legacy.cpp +++ b/packages/HighFive/tests/unit/test_legacy.cpp @@ -36,3 +36,39 @@ TEST_CASE("HighFiveReadWriteConsts") { } } } + +TEST_CASE("Array of char pointers") { + // Currently, serializing an `std::vector<char*>` as + // fixed or variable length strings doesn't work. + // + // This isn't a test of correctness. Rather it asserts the fact that + // something doesn't work in HighFive. Knowing it doesn't work is useful + // for developers, but could change in the future. + + const std::string file_name = "vector_char_pointer.h5"; + + File file(file_name, File::Truncate); + + size_t n_strings = 3; + size_t n_chars = 4; + char storage[3][4] = {"foo", "bar", "000"}; + auto strings = std::vector<char*>(n_strings); + + for (size_t i = 0; i < n_strings; ++i) { + strings[i] = static_cast<char*>(storage[i]); + } + + auto filespace = DataSpace({n_strings}); + + SECTION("fixed length") { + auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated); + auto dset = file.createDataSet("dset", filespace, datatype); + REQUIRE_THROWS(dset.write(strings)); + } + + SECTION("variable length") { + auto datatype = VariableLengthStringType(); + auto dset = file.createDataSet("dset", filespace, datatype); + REQUIRE_THROWS(dset.write(strings)); + } +} diff --git a/packages/HighFive/tests/unit/test_opencv.cpp b/packages/HighFive/tests/unit/test_opencv.cpp new file mode 100644 index 000000000..b27c06daa --- /dev/null +++ b/packages/HighFive/tests/unit/test_opencv.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c), 2024, Blue Brain Project - EPFL + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ + +#if HIGHFIVE_TEST_OPENCV + +#include <catch2/catch_test_macros.hpp> +#include <catch2/catch_template_test_macros.hpp> +#include <catch2/matchers/catch_matchers_vector.hpp> + +#include <highfive/highfive.hpp> +#include <highfive/experimental/opencv.hpp> +#include "tests_high_five.hpp" +#include "create_traits.hpp" + +using namespace HighFive; +using Catch::Matchers::Equals; + +TEST_CASE("OpenCV") { + auto file = File("rw_opencv.h5", File::Truncate); + + auto a = cv::Mat_<double>(3, 5); + auto dset = file.createDataSet("a", a); + auto b = dset.read<cv::Mat_<double>>(); + REQUIRE(a(0, 0) == b(0, 0)); + + auto va = std::vector<cv::Mat_<double>>(7, cv::Mat_<double>(3, 5)); + auto vdset = file.createDataSet("va", va); + auto vb = vdset.read<std::vector<cv::Mat_<double>>>(); + REQUIRE(vb.size() == va.size()); + REQUIRE(vb[0](0, 0) == va[0](0, 0)); +} + +TEST_CASE("OpenCV subarrays") { + auto file = File("rw_opencv_subarray.h5", File::Truncate); + + auto a = cv::Mat_<double>(3, 13); + + SECTION("write") { + auto sa = cv::Mat_<double>(a.colRange(1, 4)); + REQUIRE_THROWS(file.createDataSet("a", sa)); + } + + SECTION("read") { + auto b = cv::Mat_<double>(3, 17); + auto sb = cv::Mat_<double>(a.colRange(0, 13)); + auto dset = file.createDataSet("a", a); + + // Creates a new `Mat_` in `sb`. + dset.read(sb); + } +} + +#endif diff --git a/packages/HighFive/tests/unit/test_string.cpp b/packages/HighFive/tests/unit/test_string.cpp new file mode 100644 index 000000000..ed6a4a5bf --- /dev/null +++ b/packages/HighFive/tests/unit/test_string.cpp @@ -0,0 +1,351 @@ +/* + * Copyright (c), 2017-2024, Blue Brain Project - EPFL + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ + +#include <catch2/catch_test_macros.hpp> +#include <catch2/catch_template_test_macros.hpp> +#include <catch2/matchers/catch_matchers_vector.hpp> + +#include <highfive/highfive.hpp> +#include "tests_high_five.hpp" +#include "create_traits.hpp" + + +using namespace HighFive; +using Catch::Matchers::Equals; + +TEST_CASE("StringType") { + SECTION("enshrine-defaults") { + auto fixed_length = FixedLengthStringType(32, StringPadding::SpacePadded); + auto variable_length = VariableLengthStringType(); + + REQUIRE(fixed_length.getCharacterSet() == CharacterSet::Ascii); + REQUIRE(variable_length.getCharacterSet() == CharacterSet::Ascii); + } + + SECTION("fixed-length") { + auto fixed_length = + FixedLengthStringType(32, StringPadding::SpacePadded, CharacterSet::Utf8); + auto string_type = fixed_length.asStringType(); + + REQUIRE(string_type.getId() == fixed_length.getId()); + REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8); + REQUIRE(string_type.getPadding() == StringPadding::SpacePadded); + REQUIRE(string_type.getSize() == 32); + REQUIRE(!string_type.isVariableStr()); + REQUIRE(string_type.isFixedLenStr()); + } + + SECTION("variable-length") { + auto variable_length = VariableLengthStringType(CharacterSet::Utf8); + auto string_type = variable_length.asStringType(); + + REQUIRE(string_type.getId() == variable_length.getId()); + REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8); + REQUIRE(string_type.isVariableStr()); + REQUIRE(!string_type.isFixedLenStr()); + } + + SECTION("atomic") { + auto atomic = AtomicType<double>(); + REQUIRE_THROWS(atomic.asStringType()); + } +} + +template <class CreateTraits> +void check_single_string(File file, size_t string_length) { + auto value = std::string(string_length, 'o'); + auto dataspace = DataSpace::From(value); + + auto n_chars = value.size() + 1; + auto n_chars_overlength = n_chars + 10; + auto fixed_length = FixedLengthStringType(n_chars, StringPadding::NullTerminated); + auto overlength_nullterm = FixedLengthStringType(n_chars_overlength, + StringPadding::NullTerminated); + auto overlength_nullpad = FixedLengthStringType(n_chars_overlength, StringPadding::NullPadded); + auto overlength_spacepad = FixedLengthStringType(n_chars_overlength, + StringPadding::SpacePadded); + auto variable_length = VariableLengthStringType(); + + SECTION("automatic") { + auto obj = CreateTraits::create(file, "auto", value); + REQUIRE(obj.template read<std::string>() == value); + } + + SECTION("fixed length") { + auto obj = CreateTraits::create(file, "fixed", dataspace, fixed_length); + obj.write(value); + REQUIRE(obj.template read<std::string>() == value); + } + + SECTION("overlength null-terminated") { + auto obj = + CreateTraits::create(file, "overlength_nullterm", dataspace, overlength_nullterm); + obj.write(value); + REQUIRE(obj.template read<std::string>() == value); + } + + SECTION("overlength null-padded") { + auto obj = CreateTraits::create(file, "overlength_nullpad", dataspace, overlength_nullpad); + obj.write(value); + auto expected = std::string(n_chars_overlength, '\0'); + expected.replace(0, value.size(), value.data()); + REQUIRE(obj.template read<std::string>() == expected); + } + + SECTION("overlength space-padded") { + auto obj = + CreateTraits::create(file, "overlength_spacepad", dataspace, overlength_spacepad); + obj.write(value); + auto expected = std::string(n_chars_overlength, ' '); + expected.replace(0, value.size(), value.data()); + REQUIRE(obj.template read<std::string>() == expected); + } + + SECTION("variable length") { + auto obj = CreateTraits::create(file, "variable", dataspace, variable_length); + obj.write(value); + REQUIRE(obj.template read<std::string>() == value); + } +} + +template <class CreateTraits> +void check_multiple_string(File file, size_t string_length) { + using value_t = std::vector<std::string>; + auto value = value_t{std::string(string_length, 'o'), std::string(string_length, 'x')}; + + auto dataspace = DataSpace::From(value); + + auto string_overlength = string_length + 10; + auto onpoint_nullpad = FixedLengthStringType(string_length, StringPadding::NullPadded); + auto onpoint_spacepad = FixedLengthStringType(string_length, StringPadding::SpacePadded); + + auto overlength_nullterm = FixedLengthStringType(string_overlength, + StringPadding::NullTerminated); + auto overlength_nullpad = FixedLengthStringType(string_overlength, StringPadding::NullPadded); + auto overlength_spacepad = FixedLengthStringType(string_overlength, StringPadding::SpacePadded); + auto variable_length = VariableLengthStringType(); + + auto check = [](const value_t actual, const value_t& expected) { + REQUIRE(actual.size() == expected.size()); + for (size_t i = 0; i < actual.size(); ++i) { + REQUIRE(actual[i] == expected[i]); + } + }; + + SECTION("automatic") { + auto obj = CreateTraits::create(file, "auto", value); + check(obj.template read<value_t>(), value); + } + + SECTION("variable length") { + auto obj = CreateTraits::create(file, "variable", dataspace, variable_length); + obj.write(value); + check(obj.template read<value_t>(), value); + } + + auto make_padded_reference = [&](char pad, size_t n) { + auto expected = std::vector<std::string>(value.size(), std::string(n, pad)); + for (size_t i = 0; i < value.size(); ++i) { + expected[i].replace(0, value[i].size(), value[i].data()); + } + + return expected; + }; + + auto check_fixed_length = [&](const std::string& label, size_t length) { + SECTION(label + " null-terminated") { + auto datatype = FixedLengthStringType(length + 1, StringPadding::NullTerminated); + auto obj = CreateTraits::create(file, label + "_nullterm", dataspace, datatype); + obj.write(value); + check(obj.template read<value_t>(), value); + } + + SECTION(label + " null-padded") { + auto datatype = FixedLengthStringType(length, StringPadding::NullPadded); + auto obj = CreateTraits::create(file, label + "_nullpad", dataspace, datatype); + obj.write(value); + auto expected = make_padded_reference('\0', length); + check(obj.template read<value_t>(), expected); + } + + SECTION(label + " space-padded") { + auto datatype = FixedLengthStringType(length, StringPadding::SpacePadded); + auto obj = CreateTraits::create(file, label + "_spacepad", dataspace, datatype); + obj.write(value); + auto expected = make_padded_reference(' ', length); + check(obj.template read<value_t>(), expected); + } + }; + + check_fixed_length("onpoint", string_length); + check_fixed_length("overlength", string_length + 5); + + + SECTION("underlength null-terminated") { + auto datatype = FixedLengthStringType(string_length, StringPadding::NullTerminated); + auto obj = CreateTraits::create(file, "underlength_nullterm", dataspace, datatype); + REQUIRE_THROWS(obj.write(value)); + } + + SECTION("underlength nullpad") { + auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullPadded); + auto obj = CreateTraits::create(file, "underlength_nullpad", dataspace, datatype); + REQUIRE_THROWS(obj.write(value)); + } + + SECTION("underlength spacepad") { + auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullTerminated); + auto obj = CreateTraits::create(file, "underlength_spacepad", dataspace, datatype); + REQUIRE_THROWS(obj.write(value)); + } +} + +TEST_CASE("HighFiveSTDString (dataset, single, short)") { + File file("std_string_dataset_single_short.h5", File::Truncate); + check_single_string<testing::DataSetCreateTraits>(file, 3); +} + +TEST_CASE("HighFiveSTDString (attribute, single, short)") { + File file("std_string_attribute_single_short.h5", File::Truncate); + check_single_string<testing::AttributeCreateTraits>(file, 3); +} + +TEST_CASE("HighFiveSTDString (dataset, single, long)") { + File file("std_string_dataset_single_long.h5", File::Truncate); + check_single_string<testing::DataSetCreateTraits>(file, 256); +} + +TEST_CASE("HighFiveSTDString (attribute, single, long)") { + File file("std_string_attribute_single_long.h5", File::Truncate); + check_single_string<testing::AttributeCreateTraits>(file, 256); +} + +TEST_CASE("HighFiveSTDString (dataset, multiple, short)") { + File file("std_string_dataset_multiple_short.h5", File::Truncate); + check_multiple_string<testing::DataSetCreateTraits>(file, 3); +} + +TEST_CASE("HighFiveSTDString (attribute, multiple, short)") { + File file("std_string_attribute_multiple_short.h5", File::Truncate); + check_multiple_string<testing::AttributeCreateTraits>(file, 3); +} + +TEST_CASE("HighFiveSTDString (dataset, multiple, long)") { + File file("std_string_dataset_multiple_long.h5", File::Truncate); + check_multiple_string<testing::DataSetCreateTraits>(file, 256); +} + +TEST_CASE("HighFiveSTDString (attribute, multiple, long)") { + File file("std_string_attribute_multiple_long.h5", File::Truncate); + check_multiple_string<testing::AttributeCreateTraits>(file, 256); +} + +TEST_CASE("HighFiveFixedString") { + const std::string file_name("array_atomic_types.h5"); + const std::string group_1("group1"); + + // Create a new file using the default property lists. + File file(file_name, File::ReadWrite | File::Create | File::Truncate); + char raw_strings[][10] = {"abcd", "1234"}; + + /// This will not compile - only char arrays - hits static_assert with a nice + /// error + // file.createDataSet<int[10]>(ds_name, DataSpace(2))); + + { // But char should be fine + auto ds = file.createDataSet<char[10]>("ds1", DataSpace(2)); + CHECK(ds.getDataType().getClass() == DataTypeClass::String); + ds.write(raw_strings); + } + + { // char[] is, by default, int8 + auto ds2 = file.createDataSet("ds2", raw_strings); + CHECK(ds2.getDataType().getClass() == DataTypeClass::Integer); + } + + { // String Truncate happens low-level if well setup + auto ds3 = file.createDataSet<char[6]>("ds3", DataSpace::FromCharArrayStrings(raw_strings)); + ds3.write(raw_strings); + } + + { // Write as raw elements from pointer (with const) + const char(*strings_fixed)[10] = raw_strings; + // With a pointer we dont know how many strings -> manual DataSpace + file.createDataSet<char[10]>("ds4", DataSpace(2)).write(strings_fixed); + } + + + { // Cant convert flex-length to fixed-length + const char* buffer[] = {"abcd", "1234"}; + SilenceHDF5 silencer; + CHECK_THROWS_AS(file.createDataSet<char[10]>("ds5", DataSpace(2)).write(buffer), + HighFive::DataSetException); + } + + { // scalar char strings + const char buffer[] = "abcd"; + file.createDataSet<char[10]>("ds6", DataSpace(1)).write(buffer); + } + + { + // Direct way of writing `std::string` as a fixed length + // HDF5 string. + + std::string value = "foo"; + auto n_chars = value.size() + 1; + + auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated); + auto dataspace = DataSpace(1); + + auto ds = file.createDataSet("ds8", dataspace, datatype); + ds.write_raw(value.data(), datatype); + + { + // Due to missing non-const overload of `data()` until C++17 we'll + // read into something else instead (don't forget the '\0'). + auto expected = std::vector<char>(n_chars, '!'); + ds.read_raw(expected.data(), datatype); + + CHECK(expected.size() == value.size() + 1); + for (size_t i = 0; i < value.size(); ++i) { + REQUIRE(expected[i] == value[i]); + } + } + +#if HIGHFIVE_CXX_STD >= 17 + { + auto expected = std::string(value.size(), '-'); + ds.read_raw(expected.data(), datatype); + + REQUIRE(expected == value); + } +#endif + } + + { + size_t n_chars = 4; + size_t n_strings = 2; + + std::vector<char> value(n_chars * n_strings, '!'); + + auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated); + auto dataspace = DataSpace(n_strings); + + auto ds = file.createDataSet("ds9", dataspace, datatype); + ds.write_raw(value.data(), datatype); + + auto expected = std::vector<char>(value.size(), '-'); + ds.read_raw(expected.data(), datatype); + + CHECK(expected.size() == value.size()); + for (size_t i = 0; i < value.size(); ++i) { + REQUIRE(expected[i] == value[i]); + } + } +} diff --git a/packages/HighFive/tests/unit/test_xtensor.cpp b/packages/HighFive/tests/unit/test_xtensor.cpp new file mode 100644 index 000000000..ac0b4d743 --- /dev/null +++ b/packages/HighFive/tests/unit/test_xtensor.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (c), 2024, Blue Brain Project - EPFL + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ +#if HIGHFIVE_TEST_XTENSOR +#include <string> +#include <sstream> + +#include <catch2/catch_template_test_macros.hpp> + +#include <highfive/highfive.hpp> +#include <xtensor/xtensor.hpp> +#include <xtensor/xview.hpp> +#include <xtensor/xio.hpp> +#include <highfive/xtensor.hpp> + +#include "data_generator.hpp" + +using namespace HighFive; + +template <size_t N> +std::array<size_t, N> asStaticShape(const std::vector<size_t>& dims) { + assert(dims.size() == N); + + std::array<size_t, N> shape; + std::copy(dims.cbegin(), dims.cend(), shape.begin()); + + return shape; +} + +TEST_CASE("xt::xarray reshape", "[xtensor]") { + const std::string file_name("rw_dataset_xarray.h5"); + + File file(file_name, File::Truncate); + + std::vector<size_t> shape{3, 2, 4}; + std::vector<size_t> compatible_shape{1, 3, 2, 4}; + std::vector<size_t> incompatible_shape{5, 2, 4}; + + xt::xarray<double> a = testing::DataGenerator<xt::xtensor<double, 3>>::create(shape); + xt::xarray<double> b(compatible_shape); + xt::xarray<double> c(incompatible_shape); + + auto dset = file.createDataSet("baz", a); + + SECTION("xarray_adaptor") { + // Changes the shape. + auto b_adapt = xt::adapt(b.data(), b.size(), xt::no_ownership(), b.shape()); + dset.read(b_adapt); + REQUIRE(b_adapt.shape() == shape); + + // But can't change the number of elements. + auto c_adapt = xt::adapt(c.data(), c.size(), xt::no_ownership(), c.shape()); + REQUIRE_THROWS(dset.read(c_adapt)); + } + + SECTION("xtensor_adaptor") { + auto b_shape = asStaticShape<4>(compatible_shape); + auto c_shape = asStaticShape<3>(incompatible_shape); + + // Doesn't change the shape: + auto b_adapt = xt::adapt(b.data(), b.size(), xt::no_ownership(), b_shape); + REQUIRE_THROWS(dset.read(b_adapt)); + + // and can't change the number of elements: + auto c_adapt = xt::adapt(c.data(), c.size(), xt::no_ownership(), c_shape); + REQUIRE_THROWS(dset.read(c_adapt)); + } +} + +TEST_CASE("xt::xview example", "[xtensor]") { + File file("rw_dataset_xview.h5", File::Truncate); + + std::vector<size_t> shape{13, 5, 7}; + xt::xarray<double> a = testing::DataGenerator<xt::xtensor<double, 3>>::create(shape); + auto c = xt::view(a, xt::range(3, 31, 4), xt::all(), xt::drop(0, 3, 4, 5)); + + auto dset = file.createDataSet("c", c); + auto d = dset.read<xt::xarray<double>>(); + auto e = dset.read<xt::xarray<double, xt::layout_type::column_major>>(); + + REQUIRE(d == c); + REQUIRE(e == c); +} + +template <class XTensor> +void check_xtensor_scalar(File& file) { + XTensor a; + a = 42.0; + REQUIRE(a.shape() == std::vector<size_t>{}); + + SECTION("read") { + auto dset = file.createDataSet("a", a); + REQUIRE(dset.template read<double>() == a(0)); + } + + SECTION("write") { + double b = -42.0; + auto dset = file.createDataSet("b", b); + REQUIRE(dset.template read<xt::xarray<double>>()(0) == b); + } +} + +TEST_CASE("xt::xarray scalar", "[xtensor]") { + File file("rw_dataset_xarray_scalar.h5", File::Truncate); + check_xtensor_scalar<xt::xarray<double>>(file); +} + +TEST_CASE("xt::xtensor scalar", "[xtensor]") { + File file("rw_dataset_xtensor_scalar.h5", File::Truncate); + check_xtensor_scalar<xt::xarray<double>>(file); +} + +template <class XTensor> +void check_xtensor_empty(File& file, const XTensor& a, const std::vector<size_t>& expected_dims) { + auto dset = file.createDataSet("a", a); + auto b = dset.template read<XTensor>(); + REQUIRE(b.size() == 0); + REQUIRE(b == a); + + auto c = std::vector<XTensor>{}; + auto c_shape = details::inspector<decltype(c)>::getDimensions(c); + REQUIRE(c_shape == expected_dims); +} + +TEST_CASE("xt::xtensor empty", "[xtensor]") { + File file("rw_dataset_xtensor_empty.h5", File::Truncate); + xt::xtensor<double, 3> a({0, 1, 1}); + check_xtensor_empty(file, a, {0, 1, 1, 1}); +} + +TEST_CASE("xt::xarray empty", "[xtensor]") { + File file("rw_dataset_xarray_empty.h5", File::Truncate); + xt::xarray<double> a(std::vector<size_t>{1, 0, 1}); + check_xtensor_empty(file, a, {0}); +} + +#endif diff --git a/packages/HighFive/tests/unit/tests_high_five.hpp b/packages/HighFive/tests/unit/tests_high_five.hpp index 25839c69e..d9a4ed34d 100644 --- a/packages/HighFive/tests/unit/tests_high_five.hpp +++ b/packages/HighFive/tests/unit/tests_high_five.hpp @@ -21,6 +21,7 @@ // The list of identifiers is taken from `Boost::Predef`. #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(__TOS_WIN__) || \ defined(__WINDOWS__) +#define NOMINMAX #include <Windows.h> #endif diff --git a/packages/HighFive/tests/unit/tests_high_five_base.cpp b/packages/HighFive/tests/unit/tests_high_five_base.cpp index bc33d5dad..f52c2dcb8 100644 --- a/packages/HighFive/tests/unit/tests_high_five_base.cpp +++ b/packages/HighFive/tests/unit/tests_high_five_base.cpp @@ -27,6 +27,7 @@ #include <highfive/highfive.hpp> #include "tests_high_five.hpp" +#include "create_traits.hpp" #ifdef HIGHFIVE_TEST_BOOST #include <highfive/boost.hpp> @@ -36,6 +37,9 @@ #include <highfive/eigen.hpp> #endif +#ifdef HIGHFIVE_TEST_SPAN +#include <highfive/span.hpp> +#endif using namespace HighFive; using Catch::Matchers::Equals; @@ -815,45 +819,6 @@ TEST_CASE("Test simple listings") { } } -TEST_CASE("StringType") { - SECTION("enshrine-defaults") { - auto fixed_length = FixedLengthStringType(32, StringPadding::SpacePadded); - auto variable_length = VariableLengthStringType(); - - REQUIRE(fixed_length.getCharacterSet() == CharacterSet::Ascii); - REQUIRE(variable_length.getCharacterSet() == CharacterSet::Ascii); - } - - SECTION("fixed-length") { - auto fixed_length = - FixedLengthStringType(32, StringPadding::SpacePadded, CharacterSet::Utf8); - auto string_type = fixed_length.asStringType(); - - REQUIRE(string_type.getId() == fixed_length.getId()); - REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8); - REQUIRE(string_type.getPadding() == StringPadding::SpacePadded); - REQUIRE(string_type.getSize() == 32); - REQUIRE(!string_type.isVariableStr()); - REQUIRE(string_type.isFixedLenStr()); - } - - SECTION("variable-length") { - auto variable_length = VariableLengthStringType(CharacterSet::Utf8); - auto string_type = variable_length.asStringType(); - - REQUIRE(string_type.getId() == variable_length.getId()); - REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8); - REQUIRE(string_type.isVariableStr()); - REQUIRE(!string_type.isFixedLenStr()); - } - - SECTION("atomic") { - auto atomic = AtomicType<double>(); - REQUIRE_THROWS(atomic.asStringType()); - } -} - - TEST_CASE("DataTypeEqualTakeBack") { const std::string file_name("h5tutr_dset.h5"); const std::string dataset_name("dset"); @@ -1462,413 +1427,161 @@ TEMPLATE_LIST_TEST_CASE("ReadWriteSzip", "[template]", dataset_test_types) { } } -TEST_CASE("CheckDimensions") { - // List of dims which can all be one-dimensional. - std::vector<std::vector<size_t>> test_cases{ - {1ul, 3ul}, {3ul, 1ul}, {1ul, 1ul, 3ul}, {3ul, 1ul, 1ul}, {1ul, 3ul, 1ul}}; - - for (const auto& dims: test_cases) { - auto actual = details::checkDimensions(dims, 1ul); +template <class CreateTraits> +void check_broadcast_scalar_memspace(File& file, + const std::string& name, + const std::vector<size_t>& dims) { + auto datatype = create_datatype<double>(); + auto obj = CreateTraits::create(file, name, DataSpace(dims), datatype); - INFO("dims = " + details::format_vector(dims) + ", n_dims = 1"); - CHECK(actual); - - INFO("dims = " + details::format_vector(dims) + ", n_dims = 1"); - CHECK(!details::checkDimensions(dims, dims.size() + 1)); - } + double expected = 3.0; + obj.write(expected); - CHECK(details::checkDimensions(std::vector<size_t>{1ul}, 0ul)); - CHECK(details::checkDimensions(std::vector<size_t>{1ul}, 1ul)); - - CHECK(!details::checkDimensions(std::vector<size_t>{0ul}, 0ul)); - CHECK(!details::checkDimensions(std::vector<size_t>{2ul}, 0ul)); - - CHECK(!details::checkDimensions(std::vector<size_t>{1ul, 2ul, 3ul}, 2ul)); - CHECK(details::checkDimensions(std::vector<size_t>{3ul, 2ul, 1ul}, 2ul)); - - CHECK(details::checkDimensions(std::vector<size_t>{1ul, 1ul, 1ul, 1ul}, 1ul)); - - CHECK(details::checkDimensions(std::vector<size_t>{}, 0ul)); - CHECK(!details::checkDimensions(std::vector<size_t>{}, 1ul)); - CHECK(!details::checkDimensions(std::vector<size_t>{}, 2ul)); + auto actual = obj.template read<double>(); + CHECK(actual == expected); } +TEST_CASE("Broadcast scalar memspace, dset") { + File file("h5_broadcast_scalar_memspace_dset.h5", File::Truncate); -TEST_CASE("SqueezeDimensions") { - SECTION("possible") { - // List of testcases: the first number is n_dims then the input dimensions - // and finally the squeezed dimensions. - std::vector<std::tuple<size_t, std::vector<size_t>, std::vector<size_t>>> test_cases{ - {1ul, {3ul, 1ul}, {3ul}}, - - {1ul, {1ul, 1ul, 1ul}, {1ul}}, - - {1ul, {1ul, 3ul, 1ul}, {3ul}}, - - {1ul, {3ul, 1ul, 1ul}, {3ul}}, - {2ul, {3ul, 1ul, 1ul}, {3ul, 1ul}}, - {3ul, {3ul, 1ul, 1ul}, {3ul, 1ul, 1ul}}, - - {3ul, {2ul, 1ul, 3ul}, {2ul, 1ul, 3ul}}}; - - for (const auto& tc: test_cases) { - auto n_dim_requested = std::get<0>(tc); - auto dims = std::get<1>(tc); - auto expected = std::get<2>(tc); - auto actual = details::squeezeDimensions(dims, n_dim_requested); - - CHECK(actual == expected); - } + SECTION("[1]") { + check_broadcast_scalar_memspace<testing::DataSetCreateTraits>(file, "dset", {1}); } - SECTION("impossible") { - // List of testcases: the first number is n_dims then the input dimensions - // and finally the squeezed dimensions. - std::vector<std::tuple<size_t, std::vector<size_t>>> test_cases{{1ul, {1ul, 2ul, 3ul}}, - {2ul, {1ul, 2ul, 3ul, 1ul}}, - - {1ul, {2ul, 1ul, 3ul}}, - {2ul, {2ul, 1ul, 3ul}}}; - - for (const auto& tc: test_cases) { - auto n_dim_requested = std::get<0>(tc); - auto dims = std::get<1>(tc); - - CHECK_THROWS(details::squeezeDimensions(dims, n_dim_requested)); - } + SECTION("[1, 1, 1]") { + check_broadcast_scalar_memspace<testing::DataSetCreateTraits>(file, "dset", {1, 1, 1}); } } -void check_broadcast_1d(HighFive::File& file, - const std::vector<size_t> dims, - const std::string& dataset_name) { - // This checks that: - // - we can write 1D array into 2D dataset. - // - we can read 2D dataset into a 1D array. - std::vector<double> input_data{5.0, 6.0, 7.0}; - +TEST_CASE("Broadcast scalar memspace, attr") { + File file("h5_broadcast_scalar_memspace_attr.h5", File::Truncate); - DataSpace dataspace(dims); - DataSet dataset = file.createDataSet(dataset_name, dataspace, AtomicType<double>()); - - dataset.write(input_data); - - { - std::vector<double> read_back; - dataset.read(read_back); - - CHECK(read_back == input_data); + SECTION("[1]") { + check_broadcast_scalar_memspace<testing::AttributeCreateTraits>(file, "attr", {1}); } - { - auto read_back = dataset.read<std::vector<double>>(); - CHECK(read_back == input_data); + SECTION("[1, 1, 1]") { + check_broadcast_scalar_memspace<testing::AttributeCreateTraits>(file, "attr", {1, 1, 1}); } } -// Broadcasting is supported -TEST_CASE("ReadInBroadcastDims") { - const std::string file_name("h5_broadcast_dset.h5"); - const std::string dataset_name("dset"); +template <class CreateTraits> +void check_broadcast_scalar_filespace(File& file, const std::string& name) { + auto datatype = create_datatype<double>(); + auto obj = CreateTraits::create(file, name, DataSpace::Scalar(), datatype); - // Create a new file using the default property lists. - File file(file_name, File::Truncate); + auto value = std::vector<double>{3.0}; - SECTION("one-dimensional (1, 3)") { - check_broadcast_1d(file, {1, 3}, dataset_name + "_a"); - } - - SECTION("one-dimensional (3, 1)") { - check_broadcast_1d(file, {3, 1}, dataset_name + "_b"); - } - - SECTION("two-dimensional (2, 3, 1)") { - std::vector<size_t> dims{2, 3, 1}; - std::vector<std::vector<double>> input_data_2d{{2.0, 3.0, 4.0}, {10.0, 11.0, 12.0}}; + REQUIRE_THROWS(obj.write(value)); + REQUIRE_THROWS(obj.template read<std::vector<double>>()); + REQUIRE_THROWS(obj.read(value)); +} - DataSpace dataspace(dims); - DataSet dataset = file.createDataSet(dataset_name + "_c", dataspace, AtomicType<double>()); +TEST_CASE("Broadcast scalar filespace, dset") { + File file("h5_broadcast_scalar_filespace_dset.h5", File::Truncate); + check_broadcast_scalar_filespace<testing::DataSetCreateTraits>(file, "dset"); +} - dataset.write(input_data_2d); +TEST_CASE("Broadcast scalar filespace, attr") { + File file("h5_broadcast_scalar_filespace_attr.h5", File::Truncate); + check_broadcast_scalar_filespace<testing::AttributeCreateTraits>(file, "attr"); +} - auto check = [](const std::vector<std::vector<double>>& lhs, - const std::vector<std::vector<double>>& rhs) { - CHECK(lhs.size() == rhs.size()); - for (size_t i = 0; i < rhs.size(); ++i) { - CHECK(lhs[i].size() == rhs[i].size()); +TEST_CASE("squeeze") { + CHECK(detail::squeeze({}, {}) == std::vector<size_t>{}); + CHECK(detail::squeeze({3, 1, 1}, {}) == std::vector<size_t>{3, 1, 1}); + CHECK(detail::squeeze({3, 1, 1}, {2, 1}) == std::vector<size_t>{3}); + CHECK(detail::squeeze({1, 3, 1, 2}, {2, 0}) == std::vector<size_t>{3, 2}); - for (size_t j = 0; j < rhs[i].size(); ++j) { - CHECK(lhs[i][j] == rhs[i][j]); - } - } - }; + CHECK_THROWS(detail::squeeze({3, 1, 1}, {3})); + CHECK_THROWS(detail::squeeze({3, 1, 1}, {0})); + CHECK_THROWS(detail::squeeze({}, {0})); +} - { - std::vector<std::vector<double>> read_back; - dataset.read(read_back); +template <class CreateTraits> +void check_modify_memspace(File& file, const std::string& name) { + auto expected_values = std::vector<double>{1.0, 2.0, 3.0}; + auto values = std::vector<std::vector<double>>{expected_values}; - check(read_back, input_data_2d); - } + auto obj = CreateTraits::create(file, name, values); + SECTION("squeeze") { + auto actual_values = obj.squeezeMemSpace({0}).template read<std::vector<double>>(); - { - auto read_back = dataset.read<std::vector<std::vector<double>>>(); - check(read_back, input_data_2d); + REQUIRE(actual_values.size() == expected_values.size()); + for (size_t i = 0; i < actual_values.size(); ++i) { + REQUIRE(actual_values[i] == expected_values[i]); } } - SECTION("one-dimensional fixed length string") { - std::vector<size_t> dims{1, 1, 2}; - char input_data[2] = "a"; - - DataSpace dataspace(dims); - DataSet dataset = file.createDataSet(dataset_name + "_d", dataspace, AtomicType<char>()); - dataset.write(input_data); - - { - char read_back[2]; - dataset.read(read_back); + SECTION("reshape") { + auto actual_values = obj.reshapeMemSpace({3}).template read<std::vector<double>>(); - CHECK(read_back[0] == 'a'); - CHECK(read_back[1] == '\0'); + REQUIRE(actual_values.size() == expected_values.size()); + for (size_t i = 0; i < actual_values.size(); ++i) { + REQUIRE(actual_values[i] == expected_values[i]); } } } - -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 HIGHFIVE_TEST_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 HIGHFIVE_TEST_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; - } - } +TEST_CASE("Modify MemSpace, dset") { + File file("h5_modify_memspace_dset.h5", File::Truncate); + check_modify_memspace<testing::DataSetCreateTraits>(file, "dset"); } -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); +TEST_CASE("Modify MemSpace, attr") { + File file("h5_modify_memspace_attr.h5", File::Truncate); + check_modify_memspace<testing::AttributeCreateTraits>(file, "attr"); } -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); - } -}; +template <class CreateTraits> +void check_modify_scalar_filespace(File& file, const std::string& name) { + auto expected_value = 3.0; -struct ReadWriteDataSet { - template <class Container> - static void create(HighFive::File& file, const std::string& name, const Container& container) { - file.createDataSet(name, container); - } + auto obj = CreateTraits::create(file, name, expected_value); + SECTION("reshape") { + auto actual_values = obj.reshapeMemSpace({1}).template read<std::vector<double>>(); - 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); + REQUIRE(actual_values.size() == 1); + REQUIRE(actual_values[0] == expected_value); } } -template <class CreateContainer> -void check_empty_dataset(const std::vector<size_t>& dims) { - check_empty_read_write_cycle<ReadWriteDataSet, CreateContainer>(dims); +TEST_CASE("Modify Scalar FileSpace, dset") { + File file("h5_modify_scalar_filespace_dset.h5", File::Truncate); + check_modify_scalar_filespace<testing::DataSetCreateTraits>(file, "dset"); } -template <class CreateContainer> -void check_empty_attribute(const std::vector<size_t>& dims) { - check_empty_read_write_cycle<ReadWriteAttribute, CreateContainer>(dims); +TEST_CASE("Modify Scalar FileSpace, attr") { + File file("h5_modify_scalar_filespace_attr.h5", File::Truncate); + check_modify_scalar_filespace<testing::AttributeCreateTraits>(file, "attr"); } -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); - } +template <class CreateTraits> +void check_modify_scalar_memspace(File& file, const std::string& name) { + auto expected_value = std::vector<double>{3.0}; - SECTION("Empty attribute") { - check_empty_attribute<CreateContainer>(dims); + auto obj = CreateTraits::create(file, name, expected_value); + SECTION("squeeze") { + auto actual_value = obj.squeezeMemSpace({0}).template read<double>(); + REQUIRE(actual_value == expected_value[0]); } -} - -#ifdef HIGHFIVE_TEST_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}); + SECTION("reshape") { + auto actual_value = obj.reshapeMemSpace({}).template read<double>(); + REQUIRE(actual_value == expected_value[0]); } } -template <> -void check_empty_eigen<2>(const std::vector<size_t>& dims) { - SECTION("Eigen::Matrix") { - check_empty_everything<CreateEmptyEigenMatrix>(dims); - } +TEST_CASE("Modify Scalar MemSpace, dset") { + File file("h5_modify_scalar_memspace_dset.h5", File::Truncate); + check_modify_scalar_memspace<testing::DataSetCreateTraits>(file, "dset"); } -#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 HIGHFIVE_TEST_BOOST - SECTION("boost::multi_array") { - check_empty_everything<CreateEmptyBoostMultiArray<ndim>>(dims); - } -#endif -#ifdef HIGHFIVE_TEST_EIGEN - check_empty_eigen<ndim>(dims); -#endif +TEST_CASE("Modify Scalar MemSpace, attr") { + File file("h5_modify_scalar_memspace_attr.h5", File::Truncate); + check_modify_scalar_memspace<testing::AttributeCreateTraits>(file, "attr"); } -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"); @@ -2284,333 +1997,6 @@ TEST_CASE("DirectWriteBool") { } -class ForwardToAttribute { - public: - ForwardToAttribute(const HighFive::File& file) - : _file(file) {} - - template <class T> - HighFive::Attribute create(const std::string& name, const T& value) { - return _file.createAttribute(name, value); - } - - HighFive::Attribute create(const std::string& name, - const HighFive::DataSpace filespace, - const HighFive::DataType& datatype) { - return _file.createAttribute(name, filespace, datatype); - } - - HighFive::Attribute get(const std::string& name) { - return _file.getAttribute(name); - } - - private: - HighFive::File _file; -}; - -class ForwardToDataSet { - public: - ForwardToDataSet(const HighFive::File& file) - : _file(file) {} - - template <class T> - HighFive::DataSet create(const std::string& name, const T& value) { - return _file.createDataSet(name, value); - } - - HighFive::DataSet create(const std::string& name, - const HighFive::DataSpace filespace, - const HighFive::DataType& datatype) { - return _file.createDataSet(name, filespace, datatype); - } - - HighFive::DataSet get(const std::string& name) { - return _file.getDataSet(name); - } - - private: - HighFive::File _file; -}; - -template <class Proxy> -void check_single_string(Proxy proxy, size_t string_length) { - auto value = std::string(string_length, 'o'); - auto dataspace = DataSpace::From(value); - - auto n_chars = value.size() + 1; - auto n_chars_overlength = n_chars + 10; - auto fixed_length = FixedLengthStringType(n_chars, StringPadding::NullTerminated); - auto overlength_nullterm = FixedLengthStringType(n_chars_overlength, - StringPadding::NullTerminated); - auto overlength_nullpad = FixedLengthStringType(n_chars_overlength, StringPadding::NullPadded); - auto overlength_spacepad = FixedLengthStringType(n_chars_overlength, - StringPadding::SpacePadded); - auto variable_length = VariableLengthStringType(); - - SECTION("automatic") { - proxy.create("auto", value); - REQUIRE(proxy.get("auto").template read<std::string>() == value); - } - - SECTION("fixed length") { - proxy.create("fixed", dataspace, fixed_length).write(value); - REQUIRE(proxy.get("fixed").template read<std::string>() == value); - } - - SECTION("overlength null-terminated") { - proxy.create("overlength_nullterm", dataspace, overlength_nullterm).write(value); - REQUIRE(proxy.get("overlength_nullterm").template read<std::string>() == value); - } - - SECTION("overlength null-padded") { - proxy.create("overlength_nullpad", dataspace, overlength_nullpad).write(value); - auto expected = std::string(n_chars_overlength, '\0'); - expected.replace(0, value.size(), value.data()); - REQUIRE(proxy.get("overlength_nullpad").template read<std::string>() == expected); - } - - SECTION("overlength space-padded") { - proxy.create("overlength_spacepad", dataspace, overlength_spacepad).write(value); - auto expected = std::string(n_chars_overlength, ' '); - expected.replace(0, value.size(), value.data()); - REQUIRE(proxy.get("overlength_spacepad").template read<std::string>() == expected); - } - - SECTION("variable length") { - proxy.create("variable", dataspace, variable_length).write(value); - REQUIRE(proxy.get("variable").template read<std::string>() == value); - } -} - -template <class Proxy> -void check_multiple_string(Proxy proxy, size_t string_length) { - using value_t = std::vector<std::string>; - auto value = value_t{std::string(string_length, 'o'), std::string(string_length, 'x')}; - - auto dataspace = DataSpace::From(value); - - auto string_overlength = string_length + 10; - auto onpoint_nullpad = FixedLengthStringType(string_length, StringPadding::NullPadded); - auto onpoint_spacepad = FixedLengthStringType(string_length, StringPadding::SpacePadded); - - auto overlength_nullterm = FixedLengthStringType(string_overlength, - StringPadding::NullTerminated); - auto overlength_nullpad = FixedLengthStringType(string_overlength, StringPadding::NullPadded); - auto overlength_spacepad = FixedLengthStringType(string_overlength, StringPadding::SpacePadded); - auto variable_length = VariableLengthStringType(); - - auto check = [](const value_t actual, const value_t& expected) { - REQUIRE(actual.size() == expected.size()); - for (size_t i = 0; i < actual.size(); ++i) { - REQUIRE(actual[i] == expected[i]); - } - }; - - SECTION("automatic") { - proxy.create("auto", value); - check(proxy.get("auto").template read<value_t>(), value); - } - - SECTION("variable length") { - proxy.create("variable", dataspace, variable_length).write(value); - check(proxy.get("variable").template read<value_t>(), value); - } - - auto make_padded_reference = [&](char pad, size_t n) { - auto expected = std::vector<std::string>(value.size(), std::string(n, pad)); - for (size_t i = 0; i < value.size(); ++i) { - expected[i].replace(0, value[i].size(), value[i].data()); - } - - return expected; - }; - - auto check_fixed_length = [&](const std::string& label, size_t length) { - SECTION(label + " null-terminated") { - auto datatype = FixedLengthStringType(length + 1, StringPadding::NullTerminated); - proxy.create(label + "_nullterm", dataspace, datatype).write(value); - check(proxy.get(label + "_nullterm").template read<value_t>(), value); - } - - SECTION(label + " null-padded") { - auto datatype = FixedLengthStringType(length, StringPadding::NullPadded); - proxy.create(label + "_nullpad", dataspace, datatype).write(value); - auto expected = make_padded_reference('\0', length); - check(proxy.get(label + "_nullpad").template read<value_t>(), expected); - } - - SECTION(label + " space-padded") { - auto datatype = FixedLengthStringType(length, StringPadding::SpacePadded); - proxy.create(label + "_spacepad", dataspace, datatype).write(value); - auto expected = make_padded_reference(' ', length); - check(proxy.get(label + "_spacepad").template read<value_t>(), expected); - } - }; - - check_fixed_length("onpoint", string_length); - check_fixed_length("overlength", string_length + 5); - - - SECTION("underlength null-terminated") { - auto datatype = FixedLengthStringType(string_length, StringPadding::NullTerminated); - REQUIRE_THROWS(proxy.create("underlength_nullterm", dataspace, datatype).write(value)); - } - - SECTION("underlength nullpad") { - auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullPadded); - REQUIRE_THROWS(proxy.create("underlength_nullpad", dataspace, datatype).write(value)); - } - - SECTION("underlength spacepad") { - auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullTerminated); - REQUIRE_THROWS(proxy.create("underlength_spacepad", dataspace, datatype).write(value)); - } -} - -TEST_CASE("HighFiveSTDString (dataset, single, short)") { - File file("std_string_dataset_single_short.h5", File::Truncate); - check_single_string(ForwardToDataSet(file), 3); -} - -TEST_CASE("HighFiveSTDString (attribute, single, short)") { - File file("std_string_attribute_single_short.h5", File::Truncate); - check_single_string(ForwardToAttribute(file), 3); -} - -TEST_CASE("HighFiveSTDString (dataset, single, long)") { - File file("std_string_dataset_single_long.h5", File::Truncate); - check_single_string(ForwardToDataSet(file), 256); -} - -TEST_CASE("HighFiveSTDString (attribute, single, long)") { - File file("std_string_attribute_single_long.h5", File::Truncate); - check_single_string(ForwardToAttribute(file), 256); -} - -TEST_CASE("HighFiveSTDString (dataset, multiple, short)") { - File file("std_string_dataset_multiple_short.h5", File::Truncate); - check_multiple_string(ForwardToDataSet(file), 3); -} - -TEST_CASE("HighFiveSTDString (attribute, multiple, short)") { - File file("std_string_attribute_multiple_short.h5", File::Truncate); - check_multiple_string(ForwardToAttribute(file), 3); -} - -TEST_CASE("HighFiveSTDString (dataset, multiple, long)") { - File file("std_string_dataset_multiple_long.h5", File::Truncate); - check_multiple_string(ForwardToDataSet(file), 256); -} - -TEST_CASE("HighFiveSTDString (attribute, multiple, long)") { - File file("std_string_attribute_multiple_long.h5", File::Truncate); - check_multiple_string(ForwardToAttribute(file), 256); -} - -TEST_CASE("HighFiveFixedString") { - const std::string file_name("array_atomic_types.h5"); - const std::string group_1("group1"); - - // Create a new file using the default property lists. - File file(file_name, File::ReadWrite | File::Create | File::Truncate); - char raw_strings[][10] = {"abcd", "1234"}; - - /// This will not compile - only char arrays - hits static_assert with a nice - /// error - // file.createDataSet<int[10]>(ds_name, DataSpace(2))); - - { // But char should be fine - auto ds = file.createDataSet<char[10]>("ds1", DataSpace(2)); - CHECK(ds.getDataType().getClass() == DataTypeClass::String); - ds.write(raw_strings); - } - - { // char[] is, by default, int8 - auto ds2 = file.createDataSet("ds2", raw_strings); - CHECK(ds2.getDataType().getClass() == DataTypeClass::Integer); - } - - { // String Truncate happens low-level if well setup - auto ds3 = file.createDataSet<char[6]>("ds3", DataSpace::FromCharArrayStrings(raw_strings)); - ds3.write(raw_strings); - } - - { // Write as raw elements from pointer (with const) - const char(*strings_fixed)[10] = raw_strings; - // With a pointer we dont know how many strings -> manual DataSpace - file.createDataSet<char[10]>("ds4", DataSpace(2)).write(strings_fixed); - } - - - { // Cant convert flex-length to fixed-length - const char* buffer[] = {"abcd", "1234"}; - SilenceHDF5 silencer; - CHECK_THROWS_AS(file.createDataSet<char[10]>("ds5", DataSpace(2)).write(buffer), - HighFive::DataSetException); - } - - { // scalar char strings - const char buffer[] = "abcd"; - file.createDataSet<char[10]>("ds6", DataSpace(1)).write(buffer); - } - - { - // Direct way of writing `std::string` as a fixed length - // HDF5 string. - - std::string value = "foo"; - auto n_chars = value.size() + 1; - - auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated); - auto dataspace = DataSpace(1); - - auto ds = file.createDataSet("ds8", dataspace, datatype); - ds.write_raw(value.data(), datatype); - - { - // Due to missing non-const overload of `data()` until C++17 we'll - // read into something else instead (don't forget the '\0'). - auto expected = std::vector<char>(n_chars, '!'); - ds.read_raw(expected.data(), datatype); - - CHECK(expected.size() == value.size() + 1); - for (size_t i = 0; i < value.size(); ++i) { - REQUIRE(expected[i] == value[i]); - } - } - -#if HIGHFIVE_CXX_STD >= 17 - { - auto expected = std::string(value.size(), '-'); - ds.read_raw(expected.data(), datatype); - - REQUIRE(expected == value); - } -#endif - } - - { - size_t n_chars = 4; - size_t n_strings = 2; - - std::vector<char> value(n_chars * n_strings, '!'); - - auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated); - auto dataspace = DataSpace(n_strings); - - auto ds = file.createDataSet("ds9", dataspace, datatype); - ds.write_raw(value.data(), datatype); - - auto expected = std::vector<char>(value.size(), '-'); - ds.read_raw(expected.data(), datatype); - - CHECK(expected.size() == value.size()); - for (size_t i = 0; i < value.size(); ++i) { - REQUIRE(expected[i] == value[i]); - } - } -} - TEST_CASE("HighFiveReference") { const std::string file_name("h5_ref_test.h5"); const std::string dataset1_name("dset1"); diff --git a/packages/HighFive/tests/unit/tests_high_five_easy.cpp b/packages/HighFive/tests/unit/tests_high_five_easy.cpp index aa30b4e96..d10ef941b 100644 --- a/packages/HighFive/tests/unit/tests_high_five_easy.cpp +++ b/packages/HighFive/tests/unit/tests_high_five_easy.cpp @@ -243,6 +243,44 @@ TEST_CASE("H5Easy_xtensor") { CHECK(xt::all(xt::equal(B, B_r))); } +TEST_CASE("H5Easy_xtensor_column_major") { + H5Easy::File file("h5easy_xtensor_colum_major.h5", H5Easy::File::Overwrite); + + using column_major_t = xt::xtensor<double, 2, xt::layout_type::column_major>; + + xt::xtensor<double, 2> A = 100. * xt::random::randn<double>({20, 5}); + + H5Easy::dump(file, "/path/to/A", A); + + SECTION("Write column major") { + column_major_t B = A; + REQUIRE_THROWS(H5Easy::dump(file, "path/to/B", B)); + } + + SECTION("Read column major") { + REQUIRE_THROWS(H5Easy::load<column_major_t>(file, "/path/to/A")); + } +} + +TEST_CASE("H5Easy_xarray_column_major") { + H5Easy::File file("h5easy_xarray_colum_major.h5", H5Easy::File::Overwrite); + + using column_major_t = xt::xarray<double, xt::layout_type::column_major>; + + xt::xarray<double> A = 100. * xt::random::randn<double>({20, 5}); + + H5Easy::dump(file, "/path/to/A", A); + + SECTION("Write column major") { + column_major_t B = A; + REQUIRE_THROWS(H5Easy::dump(file, "path/to/B", B)); + } + + SECTION("Read column major") { + REQUIRE_THROWS(H5Easy::load<column_major_t>(file, "/path/to/A")); + } +} + TEST_CASE("H5Easy_xarray") { H5Easy::File file("h5easy_xarray.h5", H5Easy::File::Overwrite); diff --git a/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp b/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp index 60ec66cae..a261360e0 100644 --- a/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp +++ b/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp @@ -169,6 +169,15 @@ TEMPLATE_LIST_TEST_CASE("MultiArray3D", "[template]", numerical_test_types) { MultiArray3DTest<TestType>(); } +TEST_CASE("Test boost::multi_array with fortran_storage_order") { + const std::string file_name("h5_multi_array_fortran.h5"); + File file(file_name, File::ReadWrite | File::Create | File::Truncate); + + boost::multi_array<int, 2> ma(boost::extents[2][2], boost::fortran_storage_order()); + auto dset = file.createDataSet<int>("main_dset", DataSpace::From(ma)); + CHECK_THROWS_AS(dset.write(ma), DataTypeException); +} + template <typename T> void ublas_matrix_Test() { using Matrix = boost::numeric::ublas::matrix<T>; -- GitLab