diff --git a/.gitignore b/.gitignore index a1e6811..bfb11fb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ scripts venv build ._* +tensor_test_files diff --git a/CMakeLists.txt b/CMakeLists.txt index a0c78b7..231146d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_C_FLAGS "-O3 -march=native") -add_library(binsparse-rc STATIC) +add_library(binsparse-rc SHARED) add_subdirectory(include) add_subdirectory(src) diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..ba24856 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,4 @@ +-I./include +-I./build/include +-DBSP_USE_HDF5 +-I/usr/include/hdf5/serial diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7e5bbd1..1df709c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -19,5 +19,6 @@ add_example(bsp-ls) add_example(benchmark_read) add_example(benchmark_read_parallel) add_example(benchmark_write) +add_example(tensor_test) add_subdirectory(cpp) diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index 2b8dcdf..d6ad9e1 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -15,5 +15,6 @@ add_example(mtx2bsp) add_example(bsp2mtx) add_example(check_equivalence) add_example(bsp-ls) +add_example(tensor_test) add_example(benchmark_read) add_example(benchmark_write) diff --git a/examples/cpp/tensor_test.cpp b/examples/cpp/tensor_test.cpp new file mode 100644 index 0000000..677d71e --- /dev/null +++ b/examples/cpp/tensor_test.cpp @@ -0,0 +1 @@ +#include "../tensor_test.c" diff --git a/examples/simple_read.c b/examples/simple_read.c index f2a1184..a4ba9bb 100644 --- a/examples/simple_read.c +++ b/examples/simple_read.c @@ -7,7 +7,7 @@ #include int main(int argc, char** argv) { - char* file_name = "test.hdf5"; + char* file_name = (char*) "test.hdf5"; hid_t f = H5Fopen(file_name, H5F_ACC_RDWR, H5P_DEFAULT); diff --git a/examples/tensor_test.c b/examples/tensor_test.c new file mode 100644 index 0000000..cd30060 --- /dev/null +++ b/examples/tensor_test.c @@ -0,0 +1,22 @@ +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 3) { + fprintf(stderr, + "usage: ./tensor_test [file_name.h5] [output_file_name.h5]\n"); + return 1; + } + char* file_name = argv[1]; + bsp_tensor_t tensor = bsp_read_tensor(argv[1], NULL); + printf("rank: %d\n", tensor.rank); + printf("dims:"); + for (int i = 0; i < tensor.rank; i++) { + printf("%ld, ", tensor.dims[i]); + } + printf("\n"); + bsp_write_tensor(argv[2], tensor, NULL, NULL, 9); + bsp_destroy_tensor_t(tensor); + return 0; +} diff --git a/include/binsparse/read_tensor.h b/include/binsparse/read_tensor.h new file mode 100644 index 0000000..9d419ec --- /dev/null +++ b/include/binsparse/read_tensor.h @@ -0,0 +1,19 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +char* key_with_index(const char* key, size_t index); + +#ifdef BSP_USE_HDF5 +#include + +bsp_tensor_t bsp_read_tensor_from_group(hid_t f); +#endif + +bsp_tensor_t bsp_read_tensor(const char* file_name, const char* group); + +#ifdef __cplusplus +} +#endif diff --git a/include/binsparse/tensor.h b/include/binsparse/tensor.h new file mode 100644 index 0000000..8201e67 --- /dev/null +++ b/include/binsparse/tensor.h @@ -0,0 +1,130 @@ +#pragma once + +#include +#include + +typedef enum { + BSP_TENSOR_SPARSE = 0, + BSP_TENSOR_DENSE = 1, + BSP_TENSOR_ELEMENT = 2, +} bsp_level_kind_t; + +typedef struct { + bsp_level_kind_t kind; + // data here should be bsp_element_t*, bsp_sparse_t*, or bsp_dense_t* + void* data; +} bsp_level_t; + +// corresponds to BSP_TENSOR_ELEMENT +typedef struct { + bsp_array_t values; +} bsp_element_t; + +// corresponds to BSP_TENSOR_DENSE +typedef struct { + int rank; + // pointers_to, while it will only ever point to one bsp_array_t, must be kept + // as a pointer (rather than a struct) because there are cases where it MUST + // be null. + bsp_array_t* pointers_to; + // indices is supposed to be an array of bsp_array_t's. + bsp_array_t* indices; + bsp_level_t* child; +} bsp_sparse_t; + +typedef struct { + int rank; + bsp_level_t* child; +} bsp_dense_t; + +typedef struct { + int rank; + size_t* dims; + size_t* transpose; + size_t nnz; + bool is_iso; + + bsp_level_t* level; + // don't think too much about this at the moment. + bsp_structure_t structure; +} bsp_tensor_t; + +static inline bsp_tensor_t bsp_construct_default_tensor_t() { + bsp_tensor_t tensor; + tensor.structure = BSP_GENERAL; + tensor.is_iso = false; + tensor.nnz = tensor.rank = 0; + tensor.dims = NULL; + tensor.transpose = NULL; + + tensor.level = NULL; + return tensor; +} + +static void bsp_destroy_level_t(bsp_level_t* level) { + if (level == NULL) + return; + switch (level->kind) { + case BSP_TENSOR_ELEMENT: { + bsp_element_t* element = (bsp_element_t*) level->data; + bsp_destroy_array_t(element->values); + free(element); + break; + } + case BSP_TENSOR_DENSE: { + bsp_dense_t* dense = (bsp_dense_t*) level->data; + bsp_destroy_level_t(dense->child); + free(dense); + break; + } + case BSP_TENSOR_SPARSE: { + bsp_sparse_t* sparse = (bsp_sparse_t*) level->data; + + if (sparse->pointers_to != NULL) + bsp_destroy_array_t(*sparse->pointers_to); + if (sparse->indices != NULL) { + for (int i = 0; i < sparse->rank; i++) { + bsp_destroy_array_t(sparse->indices[i]); + } + } + bsp_destroy_level_t(sparse->child); + free(sparse); + break; + } + default:; + } +} + +static bsp_array_t bsp_get_tensor_values(bsp_tensor_t tensor) { + bsp_level_t* level = tensor.level; + while (level != NULL) { + switch (level->kind) { + case BSP_TENSOR_ELEMENT: { + bsp_element_t* element = (bsp_element_t*) level->data; + return element->values; + break; + } + case BSP_TENSOR_SPARSE: { + bsp_sparse_t* sparse = (bsp_sparse_t*) level->data; + level = sparse->child; + break; + } + case BSP_TENSOR_DENSE: { + bsp_dense_t* dense = (bsp_dense_t*) level->data; + level = dense->child; + break; + } + default:; + } + } + // this should never happen! + assert(false); +} + +static inline void bsp_destroy_tensor_t(bsp_tensor_t tensor) { + bsp_destroy_level_t(tensor.level); + if (tensor.dims != NULL) + free(tensor.dims); + if (tensor.transpose != NULL) + free(tensor.transpose); +} diff --git a/include/binsparse/write_tensor.h b/include/binsparse/write_tensor.h new file mode 100644 index 0000000..27e1f29 --- /dev/null +++ b/include/binsparse/write_tensor.h @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// TODO: make cJSON optional. + +#include +#include + +#ifdef BSP_USE_HDF5 +#include + +int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json, + int compression_level); +#endif + +int bsp_write_tensor(const char* fname, bsp_tensor_t tensor, const char* group, + cJSON* user_json, int compression_level); + +#ifdef __cplusplus +} +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 50ffb26..4f52770 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,5 +4,7 @@ target_sources(binsparse-rc PRIVATE src/read_matrix.c + src/read_tensor.c src/write_matrix.c + src/write_tensor.c ) diff --git a/src/read_tensor.c b/src/read_tensor.c new file mode 100644 index 0000000..01453ac --- /dev/null +++ b/src/read_tensor.c @@ -0,0 +1,209 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* +Returns "{key}-{index}" +*/ +char* key_with_index(const char* key, size_t index) { + int keylen = strlen(key); + int strsize = keylen * sizeof(char) + + (int) ((ceil(log10(index + 1)) + 1) * sizeof(char)) + 1; + char* res = (char*) malloc(strsize); + for (int i = 0; i < keylen; i++) { + res[i] = key[i]; + } + sprintf(res + keylen, "%ld", index); + res[strsize] = '\0'; + return res; +} + +bsp_tensor_t bsp_read_tensor_from_group(hid_t f) { + bsp_tensor_t tensor = bsp_construct_default_tensor_t(); + + char* json_string = bsp_read_attribute(f, (char*) "binsparse"); + + cJSON* j = cJSON_Parse(json_string); + + assert(j != NULL); + assert(cJSON_IsObject(j)); + + cJSON* binsparse = cJSON_GetObjectItemCaseSensitive(j, "binsparse"); + assert(cJSON_IsObject(binsparse)); + + cJSON* version_ = cJSON_GetObjectItemCaseSensitive(binsparse, "version"); + + assert(version_ != NULL); + + assert(cJSON_IsString(version_)); + + // nnz computation + cJSON* nnz_ = + cJSON_GetObjectItemCaseSensitive(binsparse, "number_of_stored_values"); + assert(nnz_ != NULL); + size_t nnz = cJSON_GetNumberValue(nnz_); + tensor.nnz = nnz; + + // check tensor shape. + cJSON* shape_ = cJSON_GetObjectItemCaseSensitive(binsparse, "shape"); + assert(shape_ != NULL); + tensor.rank = cJSON_GetArraySize(shape_); + size_t* dims = (size_t*) malloc(tensor.rank * sizeof(size_t)); + for (int idx = 0; idx < tensor.rank; idx++) { + dims[idx] = cJSON_GetNumberValue(cJSON_GetArrayItem(shape_, idx)); + } + tensor.dims = dims; + assert(tensor.rank > 0); + + cJSON* data_types_ = + cJSON_GetObjectItemCaseSensitive(binsparse, "data_types"); + assert(data_types_ != NULL); + + cJSON* binsparse_custom = + cJSON_GetObjectItemCaseSensitive(binsparse, "custom"); + assert(binsparse_custom != NULL); + + cJSON* transpose_ = + cJSON_GetObjectItemCaseSensitive(binsparse_custom, "transpose"); + if (transpose_ != NULL) { + size_t* transpose = (size_t*) malloc(tensor.rank * sizeof(size_t)); + for (int idx = 0; idx < tensor.rank; idx++) { + transpose[idx] = + cJSON_GetNumberValue((cJSON_GetArrayItem(transpose_, idx))); + } + tensor.transpose = transpose; + } + + cJSON* json_level = + cJSON_GetObjectItemCaseSensitive(binsparse_custom, "level"); + assert(json_level != NULL); + + bsp_level_t* cur_level = malloc(sizeof(bsp_level_t)); + tensor.level = cur_level; + + // this is effectively a pointer on dims. + size_t depth = 0; + + while (depth < tensor.rank + 1) { + cJSON* type_object = + cJSON_GetObjectItemCaseSensitive(json_level, "level_desc"); + char* type = type_object ? cJSON_GetStringValue(type_object) : NULL; + assert(type != NULL); + + // base case: working with an element. + if (strcmp(type, "element") == 0) { + bsp_array_t values = bsp_read_array(f, (char*) "values"); + cur_level->kind = BSP_TENSOR_ELEMENT; + bsp_element_t* data = malloc(sizeof(bsp_element_t)); + data->values = values; + cur_level->data = data; + depth++; + break; + } + + // compute what the rank of our current level is, and update our pointer + // accordingly. + cJSON* rank_obj = cJSON_GetObjectItemCaseSensitive(json_level, "rank"); + int rank = cJSON_GetNumberValue(rank_obj); + + if (strcmp(type, "dense") == 0) { + cur_level->kind = BSP_TENSOR_DENSE; + + bsp_dense_t* data = malloc(sizeof(bsp_dense_t)); + data->rank = rank; + data->child = malloc(sizeof(bsp_level_t)); + + cur_level->data = data; + cur_level = data->child; + + } else if (strcmp(type, "sparse") == 0) { + cur_level->kind = BSP_TENSOR_SPARSE; + + bsp_sparse_t* data = malloc(sizeof(bsp_sparse_t)); + + // initialize pointers_to, but only if the depth is not zero. + if (depth != 0) { + char* pointers_key = key_with_index("pointers_to_", depth); + data->pointers_to = malloc(sizeof(bsp_array_t)); + *data->pointers_to = bsp_read_array(f, pointers_key); + free(pointers_key); + } else { + data->pointers_to = NULL; + } + + // initialize indices + data->indices = malloc(rank * sizeof(bsp_array_t)); + for (int idx = 0; idx < rank; idx++) { + char* indices_key = key_with_index("indices_", depth + idx); + data->indices[idx] = bsp_read_array(f, indices_key); + free(indices_key); + } + + data->rank = rank; + data->child = malloc(sizeof(bsp_level_t)); + cur_level->data = data; + cur_level = data->child; + + } else { + assert(false); + } + // update the depth here. + json_level = cJSON_GetObjectItemCaseSensitive(json_level, "level"); + assert(json_level != NULL); + depth += rank; + } + if (cJSON_HasObjectItem(binsparse, "structure")) { + cJSON* structure_ = + cJSON_GetObjectItemCaseSensitive(binsparse, "structure"); + char* structure = cJSON_GetStringValue(structure_); + tensor.structure = bsp_get_structure(structure); + } + + cJSON_Delete(j); + free(json_string); + + return tensor; +} + +static inline size_t bsp_final_dot(const char* str) { + size_t dot_idx = 0; + for (size_t i = 0; str[i] != '\0'; i++) { + if (str[i] == '.') { + dot_idx = i; + } + } + return dot_idx; +} + +bsp_tensor_t bsp_read_tensor(const char* file_name, const char* group) { + if (group == NULL) { + size_t idx = bsp_final_dot(file_name); + if (strcmp(file_name + idx, ".hdf5") == 0 || + strcmp(file_name + idx, ".h5") == 0) { + hid_t f = H5Fopen(file_name, H5F_ACC_RDONLY, H5P_DEFAULT); + bsp_tensor_t tensor = bsp_read_tensor_from_group(f); + H5Fclose(f); + return tensor; + } else { + assert(false); + } + } else { + hid_t f = H5Fopen(file_name, H5F_ACC_RDONLY, H5P_DEFAULT); + hid_t g = H5Gopen1(f, group); + bsp_tensor_t matrix = bsp_read_tensor_from_group(g); + H5Gclose(g); + H5Fclose(f); + return matrix; + } +} diff --git a/src/write_tensor.c b/src/write_tensor.c new file mode 100644 index 0000000..52a4877 --- /dev/null +++ b/src/write_tensor.c @@ -0,0 +1,175 @@ +#include +#include +#include + +#include +#include +#include + +static cJSON* init_tensor_json(bsp_tensor_t tensor, cJSON* user_json) { + cJSON* j = cJSON_CreateObject(); + assert(j != NULL); + + cJSON* binsparse = cJSON_CreateObject(); + assert(binsparse != NULL); + + cJSON* binsparse_custom = cJSON_CreateObject(); + assert(binsparse_custom != NULL); + + cJSON_AddItemToObject(binsparse, "custom", binsparse_custom); + cJSON_AddItemToObject(j, "binsparse", binsparse); + + cJSON* userJsonItem; + + cJSON_ArrayForEach(userJsonItem, user_json) { + cJSON_AddItemToObject(j, userJsonItem->string, userJsonItem); + } + + cJSON_AddStringToObject(binsparse, "version", BINSPARSE_VERSION); + + cJSON* shape = cJSON_AddArrayToObject(binsparse, "shape"); + for (int i = 0; i < tensor.rank; i++) { + cJSON_AddItemToArray(shape, cJSON_CreateNumber(tensor.dims[i])); + } + + cJSON* transpose = cJSON_AddArrayToObject(binsparse_custom, "transpose"); + for (int i = 0; i < tensor.rank; i++) { + cJSON_AddItemToArray(transpose, cJSON_CreateNumber(tensor.transpose[i])); + } + + cJSON_AddNumberToObject(binsparse, "number_of_stored_values", tensor.nnz); + + if (tensor.structure != BSP_GENERAL) { + cJSON_AddStringToObject(binsparse, "structure", + bsp_get_structure_string(tensor.structure)); + } + + return j; +} + +int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json, + int compression_level) { + // bsp_matrix_t matrix; + cJSON* j = init_tensor_json(tensor, user_json); + // tensor: + cJSON* binsparse = cJSON_GetObjectItemCaseSensitive(j, "binsparse"); + assert(binsparse != NULL); + cJSON* binsparse_custom = + cJSON_GetObjectItemCaseSensitive(binsparse, "custom"); + assert(binsparse_custom != NULL); + + cJSON* data_types = cJSON_AddObjectToObject(binsparse, "data_types"); + bsp_array_t values = bsp_get_tensor_values(tensor); + + if (!tensor.is_iso) { + cJSON_AddStringToObject(data_types, "values", + bsp_get_type_string(values.type)); + } else { + char* base_type_string = bsp_get_type_string(values.type); + size_t len = strlen(base_type_string) + 6; + char* type_string = (char*) malloc(sizeof(char) * len); + + strncpy(type_string, "iso[", len); + strncpy(type_string + 4, base_type_string, len - 4); + strncpy(type_string + len - 2, "]", 2); + + cJSON_AddStringToObject(data_types, "values", type_string); + + free(type_string); + } + + // attempt to write an array. + int result = bsp_write_array(f, (char*) "values", values, compression_level); + if (result != 0) { + cJSON_Delete(j); + return result; + } + + int rank = 0; + bsp_level_t* level = tensor.level; + cJSON* json_level = cJSON_AddObjectToObject(binsparse_custom, "level"); + while (true) { + int reached_end = 0; + switch (level->kind) { + case BSP_TENSOR_SPARSE: { + bsp_sparse_t* sparse = level->data; + size_t layer_rank = sparse->rank; + cJSON_AddStringToObject(json_level, "level_desc", "sparse"); + cJSON_AddNumberToObject(json_level, "rank", layer_rank); + + if (sparse->pointers_to != NULL) { + cJSON_AddStringToObject(data_types, + key_with_index("pointers_to_", rank), + bsp_get_type_string(sparse->pointers_to->type)); + result = bsp_write_array(f, key_with_index("pointers_to_", rank), + *sparse->pointers_to, compression_level); + if (result != 0) { + cJSON_Delete(j); + return result; + } + } + + for (int i = 0; i < layer_rank; i++) { + cJSON_AddStringToObject(data_types, + key_with_index("indices_", rank + i), + bsp_get_type_string(sparse->indices[i].type)); + result = bsp_write_array(f, key_with_index("indices_", rank + i), + sparse->indices[i], compression_level); + if (result != 0) { + cJSON_Delete(j); + return result; + } + } + + rank += layer_rank; + level = sparse->child; + break; + } + case BSP_TENSOR_DENSE: { + cJSON_AddStringToObject(json_level, "level_desc", "dense"); + cJSON_AddNumberToObject(json_level, "rank", + ((bsp_dense_t*) level->data)->rank); + rank += ((bsp_dense_t*) level->data)->rank; + level = ((bsp_dense_t*) level->data)->child; + break; + } + case BSP_TENSOR_ELEMENT: { + cJSON_AddStringToObject(json_level, "level_desc", "element"); + reached_end = 1; + break; + } + default:; + } + + if (reached_end) + break; + json_level = cJSON_AddObjectToObject(json_level, "level"); + } + + char* json_string = cJSON_Print(j); + bsp_write_attribute(f, (char*) "binsparse", json_string); + free(json_string); + + return 0; +} + +int bsp_write_tensor(const char* fname, bsp_tensor_t tensor, const char* group, + cJSON* user_json, int compression_level) { + if (group == NULL) { + hid_t f = H5Fcreate(fname, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); + bsp_write_tensor_to_group(f, tensor, user_json, compression_level); + H5Fclose(f); + } else { + hid_t f; + if (access(fname, F_OK) == 0) { + f = H5Fopen(fname, H5F_ACC_RDWR, H5P_DEFAULT); + } else { + f = H5Fcreate(fname, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); + } + hid_t g = H5Gcreate1(f, group, H5P_DEFAULT); + bsp_write_tensor_to_group(g, tensor, user_json, compression_level); + H5Gclose(g); + H5Fclose(f); + } + return 0; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0dd7ef0..45ff378 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,3 +3,4 @@ # SPDX-License-Identifier: BSD-3-Clause add_subdirectory(bash) +add_subdirectory(julia) diff --git a/test/julia/CMakeLists.txt b/test/julia/CMakeLists.txt new file mode 100644 index 0000000..4c2c9d8 --- /dev/null +++ b/test/julia/CMakeLists.txt @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 Binsparse Developers +# +# SPDX-License-Identifier: BSD-3-Clause + +find_program(JULIA_PROGRAM julia) + +enable_testing() + +if(JULIA_PROGRAM) + add_test(NAME tensors.tensor_test COMMAND ${JULIA_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/tensor_test.jl) + + add_test(NAME tensors.cpp.tensor_test COMMAND ${JULIA_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/tensor_test.jl cpp) +endif() diff --git a/test/julia/tensor_test.jl b/test/julia/tensor_test.jl new file mode 100644 index 0000000..7671ffa --- /dev/null +++ b/test/julia/tensor_test.jl @@ -0,0 +1,91 @@ +using Finch; +using HDF5; + +dir = pwd(); + +if length(ARGS) == 0 + tensor_test = joinpath(dir, "../../examples/tensor_test") + test_files = joinpath(dir, "../../tensor_test_files") +else + tensor_test = joinpath(dir, "../../examples/cpp/tensor_test-cpp") + test_files = joinpath(dir, "../../tensor_test_files/cpp") +end + +mkpath(test_files) + +function tensortest(tensor::Tensor, input::AbstractString, output::AbstractString) + print("tensor_test ", input, " -> ", output, "\n") + fwrite(input, tensor) + run(`$tensor_test $input $output`) + output_tensor = fread(output) + @assert tensor == output_tensor +end + +tensortest( + Tensor( + Dense(SparseList{Int32}(SparseList{Int32}(Element{0.0,Float64,Int32}()))), + fsprand(10, 10, 10, 0.1) + ), + joinpath(test_files, "input1.bsp.h5"), + joinpath(test_files, "output1.bsp.h5") +) + + +tensortest( + Tensor( + Dense(SparseCOO{2}(Element(0.0))), + fsprand(10, 10, 10, 0.1) + ), + joinpath(test_files, "input2.bsp.h5"), + joinpath(test_files, "output2.bsp.h5") +) + +tensortest( + Tensor( + Dense(Dense(Dense(Element{0.0,Float64,Int32}()))), + fsprand(10, 10, 10, 0.1) + ), + joinpath(test_files, "input3.bsp.h5"), + joinpath(test_files, "output3.bsp.h5") +) + +tensortest( + Tensor( + SparseCOO{2}(Element(0.0)), + fsprand(10, 10, 0.1) + ), + joinpath(test_files, "input4.bsp.h5"), + joinpath(test_files, "output4.bsp.h5") +) + +tensortest( + Tensor( + Dense(SparseList{Int32}(Element{0.0,Float64,Int32}())), + [ + 0 1 0 3; + 1 0 0 4; + ] + ), + joinpath(test_files, "inputcsr.bsp.h5"), + joinpath(test_files, "outputcsr.bsp.h5") +) +tensortest( + Tensor( + Dense(SparseList{Int32}(Element{0.0,Float64,Int32}())), + fsprand(10, 10, 0.1) + ), + joinpath(test_files, "inputcsr2.bsp.h5"), + joinpath(test_files, "outputcsr2.bsp.h5") +) + +tensortest( + Tensor( + Dense(Dense(Element(0.0))), + [ + 1 1 2 3; + 6 1 5 4; + ] + ), + joinpath(test_files, "inputdense.bsp.h5"), + joinpath(test_files, "outputdense.bsp.h5") +)