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 <cstdint>
10#include <cstddef>
11#include <string>
12
13#include "utils_public.hpp"
14#include "utils_misc.hpp"
15#include "utils_type.hpp"
16#include "utils_dimensions.hpp"
17
23namespace chihaya {
24
28template<typename Index_>
29void validate_sparse_indices(const H5::DataSet& ihandle, const std::vector<std::uint64_t>& indptrs, std::size_t primary, std::size_t secondary, bool csc) {
30 ritsuko::hdf5::Stream1dNumericDataset<Index_> stream(&ihandle, sanisizer::cast<hsize_t>(indptrs.back()));
31 auto buffer = sanisizer::create<std::vector<Index_> >(stream.chunk_size());
32
33 hsize_t available = 0, at = 0;
34 auto next = [&]() -> Index_ {
35 if (at == available) {
36 at = 0;
37 available = stream.load(buffer.data());
38 }
39 return buffer[at++];
40 };
41
42 for (std::size_t p = 0; p < primary; ++p) {
43 const auto start = indptrs[p];
44 const auto end = indptrs[p + 1];
45 if (start > end) {
46 throw std::runtime_error("entries of 'indptr' must be sorted");
47 }
48 if (start == end) {
49 continue;
50 }
51
52 Index_ previous = next();
53 if (previous < 0) {
54 throw std::runtime_error("entries of 'indices' should be non-negative");
55 }
56
57 // If it's sorted in strictly increasing order, we only need to check the first entry for negative values.
58 // Similarly, we only need to check the last entry for whether it exceeds the secondary limit.
59 for (I<decltype(start)> x = start + 1; x < end; ++x) {
60 const auto i = next();
61 if (i <= previous) {
62 throw std::runtime_error("'indices' should be strictly increasing within each " + (csc ? std::string("column") : std::string("row")));
63 }
64 previous = i;
65 }
66
67 if (sanisizer::is_greater_than_or_equal(previous, secondary)) {
68 throw std::runtime_error("entries of 'indices' should be less than the number of " + (csc ? std::string("row") : std::string("column")) + "s");
69 }
70 }
71}
84inline ArrayDetails validate_sparse_matrix(const H5::Group& group, const ritsuko::Version& version, const Options& options) {
85 std::vector<std::size_t> dims;
86 ArrayType array_type;
87
88 {
89 auto shandle = group.openDataSet("shape");
90 auto sspace = shandle.getSpace();
91 if (sspace.getSimpleExtentNdims() != 1) {
92 throw std::runtime_error("'shape' dataset should be 1-dimensional");
93 }
94 hsize_t len;
95 sspace.getSimpleExtentDims(&len);
96 if (len != 2) {
97 throw std::runtime_error("'shape' dataset should have length 2");
98 }
99
100 if (version.lt(1, 1, 0)) {
101 dims = load_non_negative_integer_vector_0_99<std::size_t>(shandle, 2);
102 } else {
103 if (ritsuko::hdf5::exceeds_integer_limit(shandle, 64, false)) {
104 throw std::runtime_error("'shape' should have a datatype that can fit into a 64-bit unsigned integer");
105 }
106 dims = load_dimensions_from_uint64_contents<std::size_t>(shandle, 2);
107 }
108 }
109
110 hsize_t nnz;
111 {
112 auto dhandle = group.openDataSet("data");
113 auto dspace = dhandle.getSpace();
114 if (dspace.getSimpleExtentNdims() != 1) {
115 throw std::runtime_error("'data' dataset should be 1-dimensional");
116 }
117 dspace.getSimpleExtentDims(&nnz);
118
119 if (version.lt(1, 1, 0)) {
120 array_type = translate_type_0_99(dhandle.getTypeClass());
121 if (is_boolean_0_99(dhandle)) {
122 array_type = BOOLEAN;
123 }
124 } else {
125 auto type = load_scalar_string_attribute(dhandle, "type");
126 array_type = translate_type_1_1(type);
127 if (!options.details_only) {
128 check_type_1_1(dhandle, array_type);
129 }
130 }
131
132 if (!options.details_only) {
133 if (array_type != INTEGER && array_type != BOOLEAN && array_type != FLOAT) {
134 throw std::runtime_error("dataset should be integer, float or boolean");
135 }
136 validate_missing_placeholder(dhandle, version);
137 }
138 }
139
140 if (!options.details_only) {
141 bool csc = true;
142 if (!version.lt(1, 1, 0)) {
143 auto bhandle = group.openDataSet("by_column");
144 if (bhandle.getSpace().getSimpleExtentNdims() != 0) {
145 throw std::runtime_error("'by_column' dataset should be scalar");
146 }
147 if (ritsuko::hdf5::exceeds_integer_limit(bhandle, 8, true)) {
148 throw std::runtime_error("datatype of the 'by_column' dataset should fit into an 8-bit signed integer");
149 }
150 std::int8_t val;
151 bhandle.read(&val, H5::PredType::NATIVE_INT8);
152 csc = (val != 0);
153 }
154
155 const auto primary = (csc ? dims[1] : dims[0]);
156 const auto secondary = (csc ? dims[0] : dims[1]);
157
158 std::vector<std::uint64_t> indptrs;
159 {
160 auto iphandle = group.openDataSet("indptr");
161 auto ipspace = iphandle.getSpace();
162 if (ipspace.getSimpleExtentNdims() != 1) {
163 throw std::runtime_error("'indptr' dataset should be 1-dimensional");
164 }
165 hsize_t iplen;
166 ipspace.getSimpleExtentDims(&iplen);
167
168 if (iplen == 0 || !sanisizer::is_equal(iplen - 1, primary)) { // avoid risk of potential overflow with primary + 1.
169 throw std::runtime_error("'indptr' should have length equal to the number of " + (csc ? std::string("columns") : std::string("rows")) + " plus 1");
170 }
171
172 if (version.lt(1, 1, 0)) {
173 if (iphandle.getTypeClass() != H5T_INTEGER) {
174 throw std::runtime_error("'indptr' should be integer");
175 }
176 indptrs = load_non_negative_integer_vector_0_99<std::uint64_t>(iphandle, iplen);
177 } else {
178 if (ritsuko::hdf5::exceeds_integer_limit(iphandle, 64, false)) {
179 throw std::runtime_error("datatype of 'indptr' should fit into a 64-bit unsigned integer");
180 }
181 sanisizer::resize(indptrs, iplen);
182 iphandle.read(indptrs.data(), H5::PredType::NATIVE_UINT64);
183 }
184
185 iphandle.read(indptrs.data(), H5::PredType::NATIVE_UINT64);
186 if (indptrs[0] != 0) {
187 throw std::runtime_error("first entry of 'indptr' should be 0");
188 }
189 if (!sanisizer::is_equal(indptrs.back(), nnz)) {
190 throw std::runtime_error("last entry of 'indptr' should be equal to the length of 'data'");
191 }
192 }
193
194 {
195 auto ihandle = group.openDataSet("indices");
196 auto ispace = ihandle.getSpace();
197 if (ispace.getSimpleExtentNdims() != 1) {
198 throw std::runtime_error("'indices' dataset should be 1-dimensional");
199 }
200 hsize_t inum;
201 ispace.getSimpleExtentDims(&inum);
202 if (nnz != inum) {
203 throw std::runtime_error("'indices' and 'data' should have the same length");
204 }
205
206 if (version.lt(1, 1, 0)) {
207 if (!ritsuko::hdf5::exceeds_integer_limit(ihandle, 64, true)) {
208 validate_sparse_indices<std::int64_t>(ihandle, indptrs, primary, secondary, csc);
209 } else if (!ritsuko::hdf5::exceeds_integer_limit(ihandle, 64, false)) {
210 validate_sparse_indices<std::uint64_t>(ihandle, indptrs, primary, secondary, csc);
211 } else {
212 throw std::runtime_error("'indices' should be integer");
213 }
214
215 } else {
216 if (ritsuko::hdf5::exceeds_integer_limit(ihandle, 64, false)) {
217 throw std::runtime_error("datatype of 'indices' should fit into a 64-bit unsigned integer");
218 }
219 validate_sparse_indices<std::uint64_t>(ihandle, indptrs, primary, secondary, csc);
220 }
221 }
222
223 // Validating dimnames.
224 if (group.exists("dimnames")) {
225 validate_dimnames_internal(group, dims, version);
226 }
227 }
228
229 return ArrayDetails(array_type, std::move(dims));
230}
231
232}
233
234#endif
Namespace for all chihaya functions.
Definition binary_arithmetic.hpp:20
ArrayDetails validate_sparse_matrix(const H5::Group &group, const ritsuko::Version &version, const Options &options)
Definition sparse_matrix.hpp:84
ArrayType
Definition utils_public.hpp:28
Details about an array.
Definition utils_public.hpp:37
Validation options.
Definition utils_public.hpp:67
bool details_only
Definition utils_public.hpp:72
Various public utilities.