chihaya
Validating delayed array operations in HDF5
Loading...
Searching...
No Matches
sparse_matrix.hpp
Go to the documentation of this file.
1#ifndef CHIHAYA_SPARSE_MATRIX_HPP
2#define CHIHAYA_SPARSE_MATRIX_HPP
3
4#include "H5Cpp.h"
5#include "ritsuko/ritsuko.hpp"
6
7#include <vector>
8#include <stdexcept>
9#include <exception>
10#include <cstdint>
11#include <cstddef>
12#include <string>
13
14#include "utils_public.hpp"
15#include "utils_misc.hpp"
16#include "utils_type.hpp"
17#include "utils_dimensions.hpp"
18
24namespace chihaya {
25
29template<typename Index_>
30void validate_sparse_indices(
31 const H5::DataSet& ihandle,
32 const std::vector<std::uint64_t>& indptrs,
33 const std::size_t primary,
34 const std::size_t secondary,
35 const bool csc,
36 const hsize_t contiguous_chunk_size
37) {
38 ritsuko::hdf5::Stream1dNumericDataset<Index_> stream(
39 &ihandle,
40 sanisizer::cast<hsize_t>(indptrs.back()),
41 [&]{
42 ritsuko::hdf5::Stream1dNumericDatasetOptions opt;
43 opt.contiguous_chunk_size = contiguous_chunk_size;
44 return opt;
45 }()
46 );
47 auto buffer = sanisizer::create<std::vector<Index_> >(stream.chunk_size());
48
49 hsize_t available = 0, at = 0;
50 auto next = [&]() -> Index_ {
51 if (at == available) {
52 at = 0;
53 available = stream.load(buffer.data());
54 }
55 return buffer[at++];
56 };
57
58 for (std::size_t p = 0; p < primary; ++p) {
59 const auto start = indptrs[p];
60 const auto end = indptrs[p + 1];
61 if (start == end) {
62 continue;
63 }
64
65 Index_ previous = next();
66 if (previous < 0) {
67 throw std::runtime_error("entries should be non-negative");
68 }
69
70 // If it's sorted in strictly increasing order, we only need to check the first entry for negative values.
71 // Similarly, we only need to check the last entry for whether it exceeds the secondary limit.
72 for (I<decltype(start)> x = start + 1; x < end; ++x) {
73 const auto i = next();
74 if (i <= previous) {
75 throw std::runtime_error("entries should be strictly increasing within each " + (csc ? std::string("column") : std::string("row")));
76 }
77 previous = i;
78 }
79
80 if (sanisizer::is_greater_than_or_equal(previous, secondary)) {
81 throw std::runtime_error("entries should be less than the number of " + (csc ? std::string("row") : std::string("column")) + "s");
82 }
83 }
84}
101inline ArrayDetails validate_sparse_matrix(const H5::Group& group, const ritsuko::Version& version, const Options& options) {
102 std::vector<std::size_t> dims;
103 ArrayType array_type;
104
105 try {
106 auto shandle = group.openDataSet("shape");
107 auto sspace = shandle.getSpace();
108 if (sspace.getSimpleExtentNdims() != 1) {
109 throw std::runtime_error("expected a 1-dimensional dataset");
110 }
111 hsize_t len;
112 sspace.getSimpleExtentDims(&len);
113 if (len != 2) {
114 throw std::runtime_error("dataset should have length 2");
115 }
116
117 if (version.lt(1, 1, 0)) {
118 dims = load_non_negative_integer_vector_0_99<std::size_t>(shandle, 2);
119 } else {
120 if (ritsuko::hdf5::exceeds_integer_limit(shandle, 64, false)) {
121 throw std::runtime_error("expected a datatype that can fit into a 64-bit unsigned integer");
122 }
123 dims = load_dimensions_from_uint64_contents<std::size_t>(shandle, 2);
124 }
125 } catch (...) {
126 std::throw_with_nested(std::runtime_error("failed to validate 'shape'"));
127 }
128
129 hsize_t nnz;
130 try {
131 auto dhandle = group.openDataSet("data");
132 auto dspace = dhandle.getSpace();
133 if (dspace.getSimpleExtentNdims() != 1) {
134 throw std::runtime_error("expected a 1-dimensional dataset");
135 }
136 dspace.getSimpleExtentDims(&nnz);
137
138 if (version.lt(1, 1, 0)) {
139 array_type = translate_type_0_99(dhandle.getTypeClass());
140 if (is_boolean_0_99(dhandle)) {
141 array_type = BOOLEAN;
142 }
143 } else {
144 try {
145 auto thandle = dhandle.openAttribute("type");
146 auto type = read_scalar_string_attribute(thandle);
147 array_type = translate_type_1_1(type);
148 if (!options.details_only) {
149 check_type_1_1(dhandle, array_type);
150 }
151 } catch (...) {
152 std::throw_with_nested(std::runtime_error("failed to validate the 'type' attribute"));
153 }
154 }
155
156 if (!options.details_only) {
157 if (array_type != INTEGER && array_type != BOOLEAN && array_type != FLOAT) {
158 throw std::runtime_error("dataset should be integer, float or boolean");
159 }
160 validate_missing_placeholder(dhandle, version);
161 }
162 } catch (...) {
163 std::throw_with_nested(std::runtime_error("failed to validate 'data'"));
164 }
165
166 if (!options.details_only) {
167 bool csc = true;
168 if (version.ge(1, 1, 0)) {
169 try {
170 auto bhandle = group.openDataSet("by_column");
171 if (bhandle.getSpace().getSimpleExtentNdims() != 0) {
172 throw std::runtime_error("expected a scalar dataset");
173 }
174 if (ritsuko::hdf5::exceeds_integer_limit(bhandle, 8, true)) {
175 throw std::runtime_error("expected a datatype that can fit into an 8-bit signed integer");
176 }
177 std::int8_t val;
178 bhandle.read(&val, H5::PredType::NATIVE_INT8);
179 csc = (val != 0);
180 } catch (...) {
181 std::throw_with_nested(std::runtime_error("failed to validate 'by_column'"));
182 }
183 }
184
185 const auto primary = (csc ? dims[1] : dims[0]);
186 const auto secondary = (csc ? dims[0] : dims[1]);
187
188 std::vector<std::uint64_t> indptrs;
189 try {
190 auto iphandle = group.openDataSet("indptr");
191 auto ipspace = iphandle.getSpace();
192 if (ipspace.getSimpleExtentNdims() != 1) {
193 throw std::runtime_error("expected a 1-dimensional dataset");
194 }
195 hsize_t iplen;
196 ipspace.getSimpleExtentDims(&iplen);
197
198 if (iplen == 0 || !sanisizer::is_equal(iplen - 1, primary)) { // avoid risk of potential overflow with primary + 1.
199 throw std::runtime_error("dataset length should be equal to the number of " + (csc ? std::string("columns") : std::string("rows")) + " plus 1");
200 }
201
202 if (version.lt(1, 1, 0)) {
203 if (iphandle.getTypeClass() != H5T_INTEGER) {
204 throw std::runtime_error("expected an integer dataset");
205 }
206 indptrs = load_non_negative_integer_vector_0_99<std::uint64_t>(iphandle, iplen);
207 } else {
208 if (ritsuko::hdf5::exceeds_integer_limit(iphandle, 64, false)) {
209 throw std::runtime_error("expected a datatype that can fit into a 64-bit unsigned integer");
210 }
211 sanisizer::resize(indptrs, iplen);
212 iphandle.read(indptrs.data(), H5::PredType::NATIVE_UINT64);
213 }
214
215 iphandle.read(indptrs.data(), H5::PredType::NATIVE_UINT64);
216 if (indptrs[0] != 0) {
217 throw std::runtime_error("first entry should be 0");
218 }
219 if (!sanisizer::is_equal(indptrs.back(), nnz)) {
220 throw std::runtime_error("last entry should be equal to the length of 'data'");
221 }
222
223 for (std::size_t p = 0; p < primary; ++p) {
224 const auto start = indptrs[p];
225 const auto end = indptrs[p + 1];
226 if (start > end) {
227 throw std::runtime_error("entries must be sorted in increasing order");
228 }
229 }
230 } catch (...) {
231 std::throw_with_nested(std::runtime_error("failed to validate 'indptr'"));
232 }
233
234 try {
235 auto ihandle = group.openDataSet("indices");
236 auto ispace = ihandle.getSpace();
237 if (ispace.getSimpleExtentNdims() != 1) {
238 throw std::runtime_error("expected a 1-dimensional dataset");
239 }
240 hsize_t inum;
241 ispace.getSimpleExtentDims(&inum);
242 if (nnz != inum) {
243 throw std::runtime_error("dataset length should be the same as 'data'");
244 }
245
246 if (version.lt(1, 1, 0)) {
247 if (!ritsuko::hdf5::exceeds_integer_limit(ihandle, 64, true)) {
248 validate_sparse_indices<std::int64_t>(ihandle, indptrs, primary, secondary, csc, options.contiguous_chunk_size);
249 } else if (!ritsuko::hdf5::exceeds_integer_limit(ihandle, 64, false)) {
250 validate_sparse_indices<std::uint64_t>(ihandle, indptrs, primary, secondary, csc, options.contiguous_chunk_size);
251 } else {
252 throw std::runtime_error("expected an integer dataset");
253 }
254
255 } else {
256 if (ritsuko::hdf5::exceeds_integer_limit(ihandle, 64, false)) {
257 throw std::runtime_error("expected a datatype that can fit into a 64-bit unsigned integer");
258 }
259 validate_sparse_indices<std::uint64_t>(ihandle, indptrs, primary, secondary, csc, options.contiguous_chunk_size);
260 }
261 } catch (...) {
262 std::throw_with_nested(std::runtime_error("failed to validate 'indices'"));
263 }
264
265 if (group.exists("dimnames")) {
266 try {
267 auto dnhandle = group.openGroup("dimnames");
268 validate_dimnames_internal(dnhandle, dims, version, options.contiguous_chunk_size);
269 } catch (...) {
270 std::throw_with_nested(std::runtime_error("failed to validate 'dimnames'"));
271 }
272 }
273 }
274
275 return ArrayDetails(array_type, std::move(dims));
276}
277
278}
279
280#endif
Namespace for all chihaya functions.
Definition binary_arithmetic.hpp:21
ArrayDetails validate_sparse_matrix(const H5::Group &group, const ritsuko::Version &version, const Options &options)
Definition sparse_matrix.hpp:101
ArrayType
Definition utils_public.hpp:29
Container_ create(Value_ x, Args_ &&... args)
void resize(Container_ &container, Value_ x, Args_ &&... args)
constexpr bool is_equal(Left_ left, Right_ right)
constexpr bool is_greater_than_or_equal(Left_ left, Right_ right)
constexpr Dest_ cast(Value_ x)
Details about an array.
Definition utils_public.hpp:38
Options for validate().
Definition utils_public.hpp:79
bool details_only
Definition utils_public.hpp:84
hsize_t contiguous_chunk_size
Definition utils_public.hpp:90
Various public utilities.