takane
Validators for ArtifactDB file formats
Loading...
Searching...
No Matches
compressed_sparse_matrix.hpp
Go to the documentation of this file.
1#ifndef TAKANE_COMPRESSED_SPARSE_MATRIX_HPP
2#define TAKANE_COMPRESSED_SPARSE_MATRIX_HPP
3
4#include "H5Cpp.h"
5#include "ritsuko/ritsuko.hpp"
6
7#include "utils_public.hpp"
8#include "utils_array.hpp"
9#include "utils_json.hpp"
10
11#include <filesystem>
12#include <stdexcept>
13#include <exception>
14#include <string>
15#include <cstdint>
16#include <cstddef>
17#include <vector>
18
24namespace takane {
25
31inline void validate_compressed_sparse_matrix(const std::filesystem::path& path, const ObjectMetadata& metadata, const Options& options) {
32 const std::string type_name = "compressed_sparse_matrix"; // use a separate variable to avoid dangling reference warnings from GCC.
33
34 try {
35 const auto& type_meta = extract_json_object(metadata.other, type_name);
36 try {
37 const auto& vstring = extract_json_version_string(type_meta);
38 auto version = ritsuko::parse_version_string(vstring.c_str(), vstring.size(), /* skip_patch = */ true);
39 if (version.major != 1) {
40 throw std::runtime_error("unsupported version '" + vstring + "'");
41 }
42 } catch (...) {
43 std::throw_with_nested(std::runtime_error("failed to validate 'version'"));
44 }
45 } catch (...) {
46 std::throw_with_nested(std::runtime_error("failed to validate '" + type_name + "' in the object metadata"));
47 }
48
49 try {
50 H5::H5File handle(path / "matrix.h5", H5F_ACC_RDONLY);
51 auto ghandle = handle.openGroup(type_name);
52
53 std::string type;
54 try {
55 auto thandle = ghandle.openAttribute("type");
56 type = open_and_load_scalar_string_attribute(thandle);
57 } catch (...) {
58 std::throw_with_nested(std::runtime_error("failed to validate the 'type' attribute"));
59 }
60
61 bool is_csr = true;
62 try {
63 auto lhandle = ghandle.openAttribute("layout");
64 auto layout = open_and_load_scalar_string_attribute(lhandle);
65 if (layout == "CSC") {
66 is_csr = false;
67 } else if (layout != "CSR") {
68 throw std::runtime_error("'layout' should be either 'CSC' or 'CSR'");
69 }
70 } catch (...) {
71 std::throw_with_nested(std::runtime_error("failed to validate the 'layout' attribute"));
72 }
73
74 // Checking the shape.
75 std::vector<std::uint64_t> shape(2);
76 try {
77 auto shandle = ghandle.openDataSet("shape");
78 if (ritsuko::hdf5::exceeds_integer_limit(shandle, 64, false)) {
79 throw std::runtime_error("expected datatype to be a subset of a 64-bit unsigned integer");
80 }
81
82 auto sspace = shandle.getSpace();
83 if (sspace.getSimpleExtentNdims() != 1) {
84 throw std::runtime_error("expected a 1-dimensional dataset");
85 }
86 hsize_t len;
87 sspace.getSimpleExtentDims(&len);
88 if (len != 2) {
89 throw std::runtime_error("expected a dataset of length 2");
90 }
91
92 shandle.read(shape.data(), H5::PredType::NATIVE_UINT64);
93 } catch (...) {
94 std::throw_with_nested(std::runtime_error("failed to validate 'shape'"));
95 }
96
97 // Checking data, and getting the number of structural non-zeros.
98 hsize_t num_nonzero;
99 try {
100 auto dhandle = ghandle.openDataSet("data");
101
102 auto dspace = dhandle.getSpace();
103 if (dspace.getSimpleExtentNdims() != 1) {
104 throw std::runtime_error("expected a 1-dimensional dataset");
105 }
106 dspace.getSimpleExtentDims(&num_nonzero);
107
108 if (type == "integer") {
109 if (ritsuko::hdf5::exceeds_integer_limit(dhandle, 32, true)) {
110 throw std::runtime_error("expected datatype to fit inside a 32-bit signed integer");
111 }
112 } else if (type == "boolean") {
113 if (ritsuko::hdf5::exceeds_integer_limit(dhandle, 32, true)) {
114 throw std::runtime_error("expected datatype to fit inside a 32-bit signed integer");
115 }
116 } else if (type == "number") {
117 if (ritsuko::hdf5::exceeds_float_limit(dhandle, 64)) {
118 throw std::runtime_error("expected datatype to fit inside a 64-bit float");
119 }
120 } else {
121 throw std::runtime_error("unknown matrix type '" + type + "'");
122 }
123
124 validate_numeric_missing_placeholder(dhandle, "missing-value-placeholder");
125 } catch (...) {
126 std::throw_with_nested(std::runtime_error("failed to validate 'data'"));
127 }
128
129 // Checking and extracting the indptrs.
130 const auto primary_dim = (is_csr ? shape[0] : shape[1]);
131 std::vector<std::uint64_t> indptrs(sanisizer::sum<typename std::vector<std::uint64_t>::size_type>(primary_dim, 1));
132 try {
133 auto phandle = ghandle.openDataSet("indptr");
134 if (ritsuko::hdf5::exceeds_integer_limit(phandle, 64, false)) {
135 throw std::runtime_error("expected datatype to be a subset of a 64-bit unsigned integer");
136 }
137
138 auto pspace = phandle.getSpace();
139 if (pspace.getSimpleExtentNdims() != 1) {
140 throw std::runtime_error("expected a 1-dimensional dataset");
141 }
142 hsize_t numptrs;
143 pspace.getSimpleExtentDims(&numptrs);
144 if (!sanisizer::is_equal(numptrs, indptrs.size())) {
145 auto dimname = (is_csr ? std::string("rows") : std::string("columns"));
146 throw std::runtime_error("dataset should have length equal to the number of " + dimname + " plus 1");
147 }
148
149 phandle.read(indptrs.data(), H5::PredType::NATIVE_UINT64);
150 if (indptrs[0] != 0) {
151 throw std::runtime_error("first entry should be zero");
152 }
153 if (!sanisizer::is_equal(indptrs.back(), num_nonzero)) {
154 throw std::runtime_error("last entry should equal the number of non-zero elements");
155 }
156
157 for (I<decltype(primary_dim)> i = 1; i < primary_dim; ++i) {
158 if (indptrs[i] < indptrs[i-1]) {
159 throw std::runtime_error("pointers should be sorted in increasing order");
160 }
161 }
162 } catch (...) {
163 std::throw_with_nested(std::runtime_error("failed to validate 'indptr'"));
164 }
165
166 // Finally checking the indices.
167 try {
168 auto ihandle = ghandle.openDataSet("indices");
169 if (ritsuko::hdf5::exceeds_integer_limit(ihandle, 64, false)) {
170 throw std::runtime_error("expected datatype to be a subset of a 64-bit unsigned integer");
171 }
172
173 auto ispace = ihandle.getSpace();
174 if (ispace.getSimpleExtentNdims() != 1) {
175 throw std::runtime_error("expected a 1-dimensional dataset");
176 }
177 hsize_t len;
178 ispace.getSimpleExtentDims(&len);
179 if (num_nonzero != len) {
180 throw std::runtime_error("length of dataset should be equal to the number of non-zero elements");
181 }
182
183 StreamIterator<std::uint64_t> indstream(
184 ritsuko::hdf5::Stream1dNumericDataset<std::uint64_t>(
185 &ihandle,
186 len,
187 [&]{
188 ritsuko::hdf5::Stream1dNumericDatasetOptions opt;
189 opt.contiguous_chunk_size = options.hdf5_buffer_size;
190 return opt;
191 }()
192 )
193 );
194
195 const auto secondary_dim = (is_csr ? shape[1] : shape[0]);
196 for (I<decltype(primary_dim)> i = 0; i < primary_dim; ++i) {
197 const auto start = indptrs[i];
198 const auto end = indptrs[i + 1];
199 if (start == end) {
200 continue;
201 }
202
203 auto previous = indstream.next();
204 for (I<decltype(start)> j = start + 1; j < end; ++j) {
205 auto i = indstream.next();
206 if (previous >= i) {
207 auto dimname = (is_csr ? std::string("rows") : std::string("columns"));
208 throw std::runtime_error("entries should be strictly increasing within each " + dimname);
209 }
210 previous = i;
211 }
212
213 if (sanisizer::is_greater_than_or_equal(previous, secondary_dim)) {
214 auto dimname = (is_csr ? std::string("columns") : std::string("rows"));
215 throw std::runtime_error("entries should be less than the number of " + dimname);
216 }
217 }
218 } catch (...) {
219 std::throw_with_nested(std::runtime_error("failed to validate 'indices'"));
220 }
221
222 validate_array_dimnames(ghandle, "names", shape, options);
223 } catch (...) {
224 std::throw_with_nested(std::runtime_error("failed to validate '" + type_name + "' in 'matrix.h5'"));
225 }
226}
227
234inline std::size_t height_of_compressed_sparse_matrix(const std::filesystem::path& path, [[maybe_unused]] const ObjectMetadata& metadata, [[maybe_unused]] const Options& options) {
235 H5::H5File handle(path / "matrix.h5", H5F_ACC_RDONLY);
236 auto ghandle = handle.openGroup("compressed_sparse_matrix");
237 auto shandle = ghandle.openDataSet("shape");
238 std::vector<std::uint64_t> output(2);
239 shandle.read(output.data(), H5::PredType::NATIVE_UINT64);
240 return sanisizer::cast<std::size_t>(output.front());
241}
242
249inline std::vector<size_t> dimensions_of_compressed_sparse_matrix(const std::filesystem::path& path, [[maybe_unused]] const ObjectMetadata& metadata, [[maybe_unused]] const Options& options) {
250 H5::H5File handle(path / "matrix.h5", H5F_ACC_RDONLY);
251 auto ghandle = handle.openGroup("compressed_sparse_matrix");
252 auto shandle = ghandle.openDataSet("shape");
253 std::vector<std::uint64_t> output(2);
254 shandle.read(output.data(), H5::PredType::NATIVE_UINT64);
255 return cast_array_dimensions<std::size_t>(output);
256}
257
258}
259
260#endif
takane validation functions.
Definition _derived_from.hpp:15
std::vector< size_t > dimensions_of_compressed_sparse_matrix(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition compressed_sparse_matrix.hpp:249
void validate_compressed_sparse_matrix(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition compressed_sparse_matrix.hpp:31
std::size_t height_of_compressed_sparse_matrix(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition compressed_sparse_matrix.hpp:234
Object metadata, including the type and other fields.
Definition utils_public.hpp:29
std::unordered_map< std::string, std::shared_ptr< millijson::Base > > other
Definition utils_public.hpp:38
Validation options.
Definition utils_public.hpp:99
hsize_t hdf5_buffer_size
Definition utils_public.hpp:109
Exported utilities.