From e6ffb126a539e80207ff48b3499bc51843023902 Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Wed, 12 Feb 2025 21:10:22 -0500
Subject: [PATCH 01/24] dev environment

---
 compile_flags.txt |  2 ++
 dev.sh            | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+)
 create mode 100644 compile_flags.txt
 create mode 100755 dev.sh

diff --git a/compile_flags.txt b/compile_flags.txt
new file mode 100644
index 0000000..449b102
--- /dev/null
+++ b/compile_flags.txt
@@ -0,0 +1,2 @@
+-I./include
+-I./build/include
diff --git a/dev.sh b/dev.sh
new file mode 100755
index 0000000..a86d79b
--- /dev/null
+++ b/dev.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env sh
+
+CONTAINER='binsparse-reference-c'
+IMAGE='docker.io/junikimm717/nvim2025:finch'
+DIR="$(realpath "$(dirname "$0")")"
+
+case "$1" in
+  pull|p)
+    podman pull "$IMAGE"
+    (podman container ls | grep "$CONTAINER" > /dev/null 2>&1) && podman container rm -fv "$CONTAINER"
+    ;;
+  clear|c)
+    (podman container ls -a | grep "$CONTAINER" > /dev/null 2>&1)\
+    && {
+      podman container kill "$CONTAINER" > /dev/null 2>&1;
+      podman container rm "$CONTAINER"
+    }
+    ;;
+  *)
+    set +x
+    if ! (podman container ls -a | grep "$CONTAINER" > /dev/null 2>&1); then
+      podman run\
+        -dt\
+        --name "$CONTAINER"\
+        --group-add keep-groups\
+        -v "$DIR:/workspace"\
+        --privileged\
+        --rm\
+        "$IMAGE"
+    fi || exit 1
+    podman exec\
+      -e ENV=/root/.profile\
+      -it "$CONTAINER" /bin/bash
+    ;;
+esac

From 388acbdd896989364f25fe8ffd3d76072c803140 Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Sat, 15 Feb 2025 18:20:25 -0500
Subject: [PATCH 02/24] read tensor implemented

---
 compile_flags.txt               |   2 +
 include/binsparse/read_tensor.h |  17 +++
 include/binsparse/tensor.h      |  84 ++++++++++++++
 src/CMakeLists.txt              |   1 +
 src/read_matrix.c               |   1 +
 src/read_tensor.c               | 194 ++++++++++++++++++++++++++++++++
 6 files changed, 299 insertions(+)
 create mode 100644 include/binsparse/read_tensor.h
 create mode 100644 include/binsparse/tensor.h
 create mode 100644 src/read_tensor.c

diff --git a/compile_flags.txt b/compile_flags.txt
index 449b102..ba24856 100644
--- a/compile_flags.txt
+++ b/compile_flags.txt
@@ -1,2 +1,4 @@
 -I./include
 -I./build/include
+-DBSP_USE_HDF5
+-I/usr/include/hdf5/serial
diff --git a/include/binsparse/read_tensor.h b/include/binsparse/read_tensor.h
new file mode 100644
index 0000000..0e9a2f9
--- /dev/null
+++ b/include/binsparse/read_tensor.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef BSP_USE_HDF5
+#include <hdf5.h>
+
+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..e70ef04
--- /dev/null
+++ b/include/binsparse/tensor.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <binsparse/array.h>
+#include <binsparse/structure.h>
+
+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;
+  bsp_array_t pointers_to;
+  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 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.level = NULL;
+  return tensor;
+}
+
+static void bsp_destroy_level_t(bsp_level_t* level) {
+  switch (level->kind) {
+  case BSP_TENSOR_ELEMENT:;
+    bsp_element_t* element = level->data;
+    bsp_destroy_array_t(element->values);
+    free(element);
+    break;
+  case BSP_TENSOR_DENSE:;
+    bsp_dense_t* dense = level->data;
+    bsp_destroy_level_t(dense->child);
+    free(dense);
+    break;
+  case BSP_TENSOR_SPARSE:;
+    bsp_sparse_t* sparse = level->data;
+    bsp_destroy_array_t(*sparse->indices);
+    bsp_destroy_array_t(*sparse->pointers_to);
+    bsp_destroy_level_t(sparse->child);
+    free(sparse);
+    break;
+  default:;
+  }
+}
+
+static inline void bsp_destroy_tensor_t(bsp_tensor_t tensor) {
+  bsp_destroy_level_t(tensor.level);
+  free(tensor.dims);
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 50ffb26..22a19ff 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,5 +4,6 @@
 
 target_sources(binsparse-rc PRIVATE
   src/read_matrix.c
+  src/read_tensor.c
   src/write_matrix.c
 )
diff --git a/src/read_matrix.c b/src/read_matrix.c
index 464210c..bf2b96d 100644
--- a/src/read_matrix.c
+++ b/src/read_matrix.c
@@ -143,6 +143,7 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) {
 
   bsp_matrix_format_t format = bsp_get_matrix_format(format_string);
 
+  // isn't this never true?
   assert(format != 0);
 
   matrix.format = format;
diff --git a/src/read_tensor.c b/src/read_tensor.c
new file mode 100644
index 0000000..5ee7dea
--- /dev/null
+++ b/src/read_tensor.c
@@ -0,0 +1,194 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Binsparse Developers
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <binsparse/hdf5_wrapper.h>
+#include <binsparse/matrix.h>
+#include <binsparse/matrix_market/matrix_market_read.h>
+#include <binsparse/tensor.h>
+#include <cJSON/cJSON.h>
+#include <math.h>
+#include <string.h>
+
+static 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));
+  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_));
+
+  // TODO: check version.
+
+  cJSON* format_ = cJSON_GetObjectItemCaseSensitive(binsparse, "format");
+  assert(format_ != NULL);
+  char* format_string = cJSON_GetStringValue(format_);
+
+  // 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_tensor =
+      cJSON_GetObjectItemCaseSensitive(binsparse, "tensor");
+  assert(binsparse_tensor != NULL);
+  cJSON* json_level =
+      cJSON_GetObjectItemCaseSensitive(binsparse_tensor, "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_kind");
+    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.
+      {
+        char* pointers_key = key_with_index("pointers_to_", depth);
+        data->pointers_to = bsp_read_array(f, pointers_key);
+        free(pointers_key);
+      }
+
+      // 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 + rank);
+        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.
+    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;
+  }
+}

From 29a58e72511759908139513435ddf8c1c3e9d2e7 Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Wed, 19 Feb 2025 17:58:33 -0500
Subject: [PATCH 03/24] segfault debugging

---
 examples/CMakeLists.txt         |  1 +
 examples/tensor_test.c          | 19 +++++++++++++++++++
 include/binsparse/read_tensor.h |  2 ++
 include/binsparse/tensor.h      | 14 +++++++++++---
 src/read_tensor.c               | 27 +++++++++++++++------------
 5 files changed, 48 insertions(+), 15 deletions(-)
 create mode 100644 examples/tensor_test.c

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/tensor_test.c b/examples/tensor_test.c
new file mode 100644
index 0000000..091367f
--- /dev/null
+++ b/examples/tensor_test.c
@@ -0,0 +1,19 @@
+#include<binsparse/tensor.h>
+#include<binsparse/read_tensor.h>
+
+int main(int argc, char **argv) {
+  if (argc < 2) {
+    fprintf(stderr, "usage: ./tensor_test [file_name.h5]\n");
+    return 1;
+  }
+  char* file_name = argv[1];
+  bsp_tensor_t tensor = bsp_read_tensor(file_name, 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_destroy_tensor_t(tensor);
+  return 0;
+}
diff --git a/include/binsparse/read_tensor.h b/include/binsparse/read_tensor.h
index 0e9a2f9..9d419ec 100644
--- a/include/binsparse/read_tensor.h
+++ b/include/binsparse/read_tensor.h
@@ -4,6 +4,8 @@
 extern "C" {
 #endif
 
+char* key_with_index(const char* key, size_t index);
+
 #ifdef BSP_USE_HDF5
 #include <hdf5.h>
 
diff --git a/include/binsparse/tensor.h b/include/binsparse/tensor.h
index e70ef04..9fa577e 100644
--- a/include/binsparse/tensor.h
+++ b/include/binsparse/tensor.h
@@ -23,7 +23,7 @@ typedef struct {
 // corresponds to BSP_TENSOR_DENSE
 typedef struct {
   int rank;
-  bsp_array_t pointers_to;
+  bsp_array_t* pointers_to;
   bsp_array_t* indices;
   bsp_level_t* child;
 } bsp_sparse_t;
@@ -56,6 +56,8 @@ static inline bsp_tensor_t bsp_construct_default_tensor_t() {
 }
 
 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 = level->data;
@@ -69,8 +71,14 @@ static void bsp_destroy_level_t(bsp_level_t* level) {
     break;
   case BSP_TENSOR_SPARSE:;
     bsp_sparse_t* sparse = level->data;
-    bsp_destroy_array_t(*sparse->indices);
-    bsp_destroy_array_t(*sparse->pointers_to);
+
+    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;
diff --git a/src/read_tensor.c b/src/read_tensor.c
index 5ee7dea..48996d5 100644
--- a/src/read_tensor.c
+++ b/src/read_tensor.c
@@ -13,10 +13,13 @@
 #include <math.h>
 #include <string.h>
 
-static char* key_with_index(const char* key, size_t index) {
+/*
+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));
+                (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];
@@ -45,12 +48,6 @@ bsp_tensor_t bsp_read_tensor_from_group(hid_t f) {
 
   assert(cJSON_IsString(version_));
 
-  // TODO: check version.
-
-  cJSON* format_ = cJSON_GetObjectItemCaseSensitive(binsparse, "format");
-  assert(format_ != NULL);
-  char* format_string = cJSON_GetStringValue(format_);
-
   // nnz computation
   cJSON* nnz_ =
       cJSON_GetObjectItemCaseSensitive(binsparse, "number_of_stored_values");
@@ -117,22 +114,26 @@ bsp_tensor_t bsp_read_tensor_from_group(hid_t f) {
 
       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.
-      {
+      // 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 = bsp_read_array(f, pointers_key);
+        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 + rank);
+        char* indices_key = key_with_index("indices_", depth + idx);
         data->indices[idx] = bsp_read_array(f, indices_key);
         free(indices_key);
       }
@@ -146,6 +147,8 @@ bsp_tensor_t bsp_read_tensor_from_group(hid_t f) {
       assert(false);
     }
     // update the depth here.
+    json_level = cJSON_GetObjectItemCaseSensitive(json_level, "level");
+    assert(json_level != NULL);
     depth += rank;
   }
   if (cJSON_HasObjectItem(binsparse, "structure")) {

From 436863e6e527a1aa9f615593acd18c061c231d3f Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Wed, 19 Feb 2025 21:51:08 -0500
Subject: [PATCH 04/24] tensor writer implemented

TODO: you need to apparently set the format key.
---
 examples/tensor_test.c           |  14 +--
 include/binsparse/tensor.h       |  23 +++++
 include/binsparse/write_tensor.h |  30 +++++++
 src/CMakeLists.txt               |   1 +
 src/write_tensor.c               | 143 +++++++++++++++++++++++++++++++
 5 files changed, 205 insertions(+), 6 deletions(-)
 create mode 100644 include/binsparse/write_tensor.h
 create mode 100644 src/write_tensor.c

diff --git a/examples/tensor_test.c b/examples/tensor_test.c
index 091367f..bfa0bfc 100644
--- a/examples/tensor_test.c
+++ b/examples/tensor_test.c
@@ -1,19 +1,21 @@
-#include<binsparse/tensor.h>
-#include<binsparse/read_tensor.h>
+#include <binsparse/tensor.h>
+#include <binsparse/read_tensor.h>
+#include <binsparse/write_tensor.h>
 
-int main(int argc, char **argv) {
-  if (argc < 2) {
-    fprintf(stderr, "usage: ./tensor_test [file_name.h5]\n");
+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(file_name, NULL);
+  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/tensor.h b/include/binsparse/tensor.h
index 9fa577e..32e16a3 100644
--- a/include/binsparse/tensor.h
+++ b/include/binsparse/tensor.h
@@ -86,6 +86,29 @@ static void bsp_destroy_level_t(bsp_level_t* level) {
   }
 }
 
+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 = level->data;
+      return element->values;
+      break;
+    case BSP_TENSOR_SPARSE:;
+      bsp_sparse_t* sparse = level->data;
+      level = sparse->child;
+      break;
+    case BSP_TENSOR_DENSE:;
+      bsp_dense_t* dense = 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);
   free(tensor.dims);
diff --git a/include/binsparse/write_tensor.h b/include/binsparse/write_tensor.h
new file mode 100644
index 0000000..64497c2
--- /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<binsparse/tensor.h>
+#include <cJSON/cJSON.h>
+
+#ifdef BSP_USE_HDF5
+#include <hdf5.h>
+
+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 22a19ff..4f52770 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -6,4 +6,5 @@ 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/write_tensor.c b/src/write_tensor.c
new file mode 100644
index 0000000..2c1a983
--- /dev/null
+++ b/src/write_tensor.c
@@ -0,0 +1,143 @@
+#include <assert.h>
+#include <binsparse/tensor.h>
+#include <unistd.h>
+
+#include <binsparse/binsparse.h>
+#include <binsparse/read_tensor.h>
+#include <cJSON/cJSON.h>
+
+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_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_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);
+  cJSON* binsparse = cJSON_GetObjectItemCaseSensitive(j, "binsparse");
+  assert(binsparse != 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;
+  while (level->kind != BSP_TENSOR_ELEMENT) {
+    switch (level->kind) {
+    case BSP_TENSOR_SPARSE:;
+      bsp_sparse_t* sparse = level->data;
+      size_t layer_rank = sparse->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:;
+      rank += ((bsp_dense_t*) level->data)->rank;
+      level = ((bsp_dense_t*) level->data)->child;
+      break;
+    default:;
+    }
+  }
+
+  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;
+}

From 7150a6ef23b8913070ee341271952100dbbefa85 Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Wed, 19 Feb 2025 23:40:52 -0500
Subject: [PATCH 05/24] tensor reader/writer successfully bootstrapped

---
 src/write_tensor.c | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/src/write_tensor.c b/src/write_tensor.c
index 2c1a983..7a2ccc7 100644
--- a/src/write_tensor.c
+++ b/src/write_tensor.c
@@ -11,9 +11,12 @@ static cJSON* init_tensor_json(bsp_tensor_t tensor, cJSON* user_json) {
   assert(j != NULL);
 
   cJSON* binsparse = cJSON_CreateObject();
-
   assert(binsparse != NULL);
 
+  cJSON* binsparse_tensor = cJSON_CreateObject();
+  assert(binsparse_tensor != NULL);
+
+  cJSON_AddItemToObject(binsparse, "tensor", binsparse_tensor);
   cJSON_AddItemToObject(j, "binsparse", binsparse);
 
   cJSON* userJsonItem;
@@ -43,8 +46,13 @@ 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_tensor =
+      cJSON_GetObjectItemCaseSensitive(binsparse, "tensor");
+  assert(binsparse_tensor != NULL);
+
   cJSON* data_types = cJSON_AddObjectToObject(binsparse, "data_types");
   bsp_array_t values = bsp_get_tensor_values(tensor);
 
@@ -74,11 +82,16 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
 
   int rank = 0;
   bsp_level_t* level = tensor.level;
-  while (level->kind != BSP_TENSOR_ELEMENT) {
+  cJSON* json_level = cJSON_AddObjectToObject(binsparse_tensor, "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_kind", "sparse");
+      cJSON_AddNumberToObject(json_level, "rank", layer_rank);
+
       if (sparse->pointers_to != NULL) {
         cJSON_AddStringToObject(data_types,
                                 key_with_index("pointers_to_", rank),
@@ -107,11 +120,22 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
       level = sparse->child;
       break;
     case BSP_TENSOR_DENSE:;
+      cJSON_AddStringToObject(json_level, "level_kind", "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_kind", "element");
+      reached_end = 1;
+      break;
     default:;
     }
+
+    if (reached_end)
+      break;
+    json_level = cJSON_AddObjectToObject(json_level, "level");
   }
 
   char* json_string = cJSON_Print(j);

From b1e6d12357719046d916ad11fe9f078396d96c51 Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Thu, 20 Feb 2025 17:40:10 -0500
Subject: [PATCH 06/24] testing framework for tensor read/write implemented

---
 .gitignore                   |  1 +
 examples/cpp/CMakeLists.txt  |  1 +
 examples/cpp/tensor_test.cpp |  1 +
 examples/simple_read.c       |  2 +-
 include/binsparse/tensor.h   | 30 ++++++++++-------
 src/write_tensor.c           |  9 ++++--
 test/CMakeLists.txt          |  1 +
 test/julia/CMakeLists.txt    | 13 ++++++++
 test/julia/tensor_test.jl    | 62 ++++++++++++++++++++++++++++++++++++
 9 files changed, 104 insertions(+), 16 deletions(-)
 create mode 100644 examples/cpp/tensor_test.cpp
 create mode 100644 test/julia/CMakeLists.txt
 create mode 100644 test/julia/tensor_test.jl

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/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 <binsparse/binsparse.h>
 
 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/include/binsparse/tensor.h b/include/binsparse/tensor.h
index 32e16a3..87c1b54 100644
--- a/include/binsparse/tensor.h
+++ b/include/binsparse/tensor.h
@@ -59,18 +59,20 @@ 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 = level->data;
+  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 = level->data;
+  }
+  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 = level->data;
+  }
+  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);
@@ -82,6 +84,7 @@ static void bsp_destroy_level_t(bsp_level_t* level) {
     bsp_destroy_level_t(sparse->child);
     free(sparse);
     break;
+  }
   default:;
   }
 }
@@ -90,18 +93,21 @@ 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 = level->data;
+    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 = level->data;
+    }
+    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 = level->data;
+    }
+    case BSP_TENSOR_DENSE: {
+      bsp_dense_t* dense = (bsp_dense_t*) level->data;
       level = dense->child;
       break;
+    }
     default:;
     }
   }
diff --git a/src/write_tensor.c b/src/write_tensor.c
index 7a2ccc7..1d468a7 100644
--- a/src/write_tensor.c
+++ b/src/write_tensor.c
@@ -86,7 +86,7 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
   while (true) {
     int reached_end = 0;
     switch (level->kind) {
-    case BSP_TENSOR_SPARSE:;
+    case BSP_TENSOR_SPARSE: {
       bsp_sparse_t* sparse = level->data;
       size_t layer_rank = sparse->rank;
       cJSON_AddStringToObject(json_level, "level_kind", "sparse");
@@ -119,17 +119,20 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
       rank += layer_rank;
       level = sparse->child;
       break;
-    case BSP_TENSOR_DENSE:;
+    }
+    case BSP_TENSOR_DENSE: {
       cJSON_AddStringToObject(json_level, "level_kind", "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:;
+    }
+    case BSP_TENSOR_ELEMENT: {
       cJSON_AddStringToObject(json_level, "level_kind", "element");
       reached_end = 1;
       break;
+    }
     default:;
     }
 
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..04cb1d2
--- /dev/null
+++ b/test/julia/tensor_test.jl
@@ -0,0 +1,62 @@
+using Finch;
+using HDF5;
+
+# dir = @__DIR__();
+dir = pwd();
+
+print("Args:", ARGS, "\n");
+
+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`)
+  new_tensor = fread(output)
+  # @assert new_tensor == tensor
+end
+
+tensortest(
+  Tensor(
+    Dense(SparseList(SparseList(Element(0.0)))),
+    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)))),
+    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")
+)

From 138647f3ba585f87532e64f5dc059b4fa19aae56 Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Fri, 21 Feb 2025 08:27:43 -0500
Subject: [PATCH 07/24] tests fixed

---
 test/julia/tensor_test.jl | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/test/julia/tensor_test.jl b/test/julia/tensor_test.jl
index 04cb1d2..216a0ec 100644
--- a/test/julia/tensor_test.jl
+++ b/test/julia/tensor_test.jl
@@ -1,11 +1,8 @@
 using Finch;
 using HDF5;
 
-# dir = @__DIR__();
 dir = pwd();
 
-print("Args:", ARGS, "\n");
-
 if length(ARGS) == 0
   tensor_test = joinpath(dir, "../../examples/tensor_test")
   test_files = joinpath(dir, "../../tensor_test_files")
@@ -20,8 +17,9 @@ function tensortest(tensor::Tensor, input::AbstractString, output::AbstractStrin
   print("tensor_test ", input, " -> ", output, "\n")
   fwrite(input, tensor)
   run(`$tensor_test $input $output`)
-  new_tensor = fread(output)
-  # @assert new_tensor == tensor
+  # for whatever reason, fread returns a swizzlearray
+  new_tensor = fread(output).body
+  @assert new_tensor == tensor
 end
 
 tensortest(

From 3309097e4f7228e09cc6931704fd33058c2edeca Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Sat, 22 Feb 2025 11:50:49 -0500
Subject: [PATCH 08/24] parse transposes too

---
 include/binsparse/tensor.h |  5 ++++-
 src/read_tensor.c          | 18 +++++++++++++++---
 src/write_tensor.c         | 17 +++++++++++------
 test/julia/tensor_test.jl  |  5 ++---
 4 files changed, 32 insertions(+), 13 deletions(-)

diff --git a/include/binsparse/tensor.h b/include/binsparse/tensor.h
index 87c1b54..60dad01 100644
--- a/include/binsparse/tensor.h
+++ b/include/binsparse/tensor.h
@@ -36,6 +36,7 @@ typedef struct {
 typedef struct {
   int rank;
   size_t* dims;
+  size_t* transpose;
   size_t nnz;
   bool is_iso;
 
@@ -50,6 +51,7 @@ static inline bsp_tensor_t bsp_construct_default_tensor_t() {
   tensor.is_iso = false;
   tensor.nnz = tensor.rank = 0;
   tensor.dims = NULL;
+  tensor.transpose = NULL;
 
   tensor.level = NULL;
   return tensor;
@@ -117,5 +119,6 @@ static bsp_array_t bsp_get_tensor_values(bsp_tensor_t tensor) {
 
 static inline void bsp_destroy_tensor_t(bsp_tensor_t tensor) {
   bsp_destroy_level_t(tensor.level);
-  free(tensor.dims);
+  if (tensor.dims != NULL) free(tensor.dims);
+  if (tensor.transpose != NULL) free(tensor.transpose);
 }
diff --git a/src/read_tensor.c b/src/read_tensor.c
index 48996d5..ec552a2 100644
--- a/src/read_tensor.c
+++ b/src/read_tensor.c
@@ -70,11 +70,23 @@ bsp_tensor_t bsp_read_tensor_from_group(hid_t f) {
       cJSON_GetObjectItemCaseSensitive(binsparse, "data_types");
   assert(data_types_ != NULL);
 
-  cJSON* binsparse_tensor =
+  cJSON* binsparse_custom =
       cJSON_GetObjectItemCaseSensitive(binsparse, "tensor");
-  assert(binsparse_tensor != NULL);
+  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_tensor, "level");
+      cJSON_GetObjectItemCaseSensitive(binsparse_custom, "level");
   assert(json_level != NULL);
 
   bsp_level_t* cur_level = malloc(sizeof(bsp_level_t));
diff --git a/src/write_tensor.c b/src/write_tensor.c
index 1d468a7..d1f272e 100644
--- a/src/write_tensor.c
+++ b/src/write_tensor.c
@@ -13,10 +13,10 @@ static cJSON* init_tensor_json(bsp_tensor_t tensor, cJSON* user_json) {
   cJSON* binsparse = cJSON_CreateObject();
   assert(binsparse != NULL);
 
-  cJSON* binsparse_tensor = cJSON_CreateObject();
-  assert(binsparse_tensor != NULL);
+  cJSON* binsparse_custom = cJSON_CreateObject();
+  assert(binsparse_custom != NULL);
 
-  cJSON_AddItemToObject(binsparse, "tensor", binsparse_tensor);
+  cJSON_AddItemToObject(binsparse, "tensor", binsparse_custom);
   cJSON_AddItemToObject(j, "binsparse", binsparse);
 
   cJSON* userJsonItem;
@@ -32,6 +32,11 @@ static cJSON* init_tensor_json(bsp_tensor_t tensor, cJSON* user_json) {
     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) {
@@ -49,9 +54,9 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
   // tensor:
   cJSON* binsparse = cJSON_GetObjectItemCaseSensitive(j, "binsparse");
   assert(binsparse != NULL);
-  cJSON* binsparse_tensor =
+  cJSON* binsparse_custom =
       cJSON_GetObjectItemCaseSensitive(binsparse, "tensor");
-  assert(binsparse_tensor != NULL);
+  assert(binsparse_custom != NULL);
 
   cJSON* data_types = cJSON_AddObjectToObject(binsparse, "data_types");
   bsp_array_t values = bsp_get_tensor_values(tensor);
@@ -82,7 +87,7 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
 
   int rank = 0;
   bsp_level_t* level = tensor.level;
-  cJSON* json_level = cJSON_AddObjectToObject(binsparse_tensor, "level");
+  cJSON* json_level = cJSON_AddObjectToObject(binsparse_custom, "level");
   while (true) {
     int reached_end = 0;
     switch (level->kind) {
diff --git a/test/julia/tensor_test.jl b/test/julia/tensor_test.jl
index 216a0ec..844c758 100644
--- a/test/julia/tensor_test.jl
+++ b/test/julia/tensor_test.jl
@@ -17,9 +17,8 @@ function tensortest(tensor::Tensor, input::AbstractString, output::AbstractStrin
   print("tensor_test ", input, " -> ", output, "\n")
   fwrite(input, tensor)
   run(`$tensor_test $input $output`)
-  # for whatever reason, fread returns a swizzlearray
-  new_tensor = fread(output).body
-  @assert new_tensor == tensor
+  output_tensor = fread(output)
+  @assert tensor == output_tensor
 end
 
 tensortest(

From 4e50d1486a88283cc8cff5eb607584046268408b Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Thu, 27 Feb 2025 21:15:41 -0500
Subject: [PATCH 09/24] json reading/writing adjusted for binsparse compliance

---
 src/read_tensor.c  |  4 ++--
 src/write_tensor.c | 10 +++++-----
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/read_tensor.c b/src/read_tensor.c
index ec552a2..01453ac 100644
--- a/src/read_tensor.c
+++ b/src/read_tensor.c
@@ -71,7 +71,7 @@ bsp_tensor_t bsp_read_tensor_from_group(hid_t f) {
   assert(data_types_ != NULL);
 
   cJSON* binsparse_custom =
-      cJSON_GetObjectItemCaseSensitive(binsparse, "tensor");
+      cJSON_GetObjectItemCaseSensitive(binsparse, "custom");
   assert(binsparse_custom != NULL);
 
   cJSON* transpose_ =
@@ -97,7 +97,7 @@ bsp_tensor_t bsp_read_tensor_from_group(hid_t f) {
 
   while (depth < tensor.rank + 1) {
     cJSON* type_object =
-        cJSON_GetObjectItemCaseSensitive(json_level, "level_kind");
+        cJSON_GetObjectItemCaseSensitive(json_level, "level_desc");
     char* type = type_object ? cJSON_GetStringValue(type_object) : NULL;
     assert(type != NULL);
 
diff --git a/src/write_tensor.c b/src/write_tensor.c
index d1f272e..52a4877 100644
--- a/src/write_tensor.c
+++ b/src/write_tensor.c
@@ -16,7 +16,7 @@ static cJSON* init_tensor_json(bsp_tensor_t tensor, cJSON* user_json) {
   cJSON* binsparse_custom = cJSON_CreateObject();
   assert(binsparse_custom != NULL);
 
-  cJSON_AddItemToObject(binsparse, "tensor", binsparse_custom);
+  cJSON_AddItemToObject(binsparse, "custom", binsparse_custom);
   cJSON_AddItemToObject(j, "binsparse", binsparse);
 
   cJSON* userJsonItem;
@@ -55,7 +55,7 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
   cJSON* binsparse = cJSON_GetObjectItemCaseSensitive(j, "binsparse");
   assert(binsparse != NULL);
   cJSON* binsparse_custom =
-      cJSON_GetObjectItemCaseSensitive(binsparse, "tensor");
+      cJSON_GetObjectItemCaseSensitive(binsparse, "custom");
   assert(binsparse_custom != NULL);
 
   cJSON* data_types = cJSON_AddObjectToObject(binsparse, "data_types");
@@ -94,7 +94,7 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
     case BSP_TENSOR_SPARSE: {
       bsp_sparse_t* sparse = level->data;
       size_t layer_rank = sparse->rank;
-      cJSON_AddStringToObject(json_level, "level_kind", "sparse");
+      cJSON_AddStringToObject(json_level, "level_desc", "sparse");
       cJSON_AddNumberToObject(json_level, "rank", layer_rank);
 
       if (sparse->pointers_to != NULL) {
@@ -126,7 +126,7 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
       break;
     }
     case BSP_TENSOR_DENSE: {
-      cJSON_AddStringToObject(json_level, "level_kind", "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;
@@ -134,7 +134,7 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json,
       break;
     }
     case BSP_TENSOR_ELEMENT: {
-      cJSON_AddStringToObject(json_level, "level_kind", "element");
+      cJSON_AddStringToObject(json_level, "level_desc", "element");
       reached_end = 1;
       break;
     }

From 1683216a4b961ec61c8a8880b70927d79940950f Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Sun, 16 Mar 2025 14:21:24 -0400
Subject: [PATCH 10/24] change to shared library

---
 CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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)

From c6dcbb42da7cd506f77a741a6dc2c5b28ace9fe3 Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Mon, 31 Mar 2025 12:32:30 -0400
Subject: [PATCH 11/24] so it turns out that we need int32 indices

---
 include/binsparse/tensor.h |  4 ++++
 test/julia/tensor_test.jl  | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/include/binsparse/tensor.h b/include/binsparse/tensor.h
index 60dad01..3909318 100644
--- a/include/binsparse/tensor.h
+++ b/include/binsparse/tensor.h
@@ -23,7 +23,11 @@ typedef struct {
 // 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;
diff --git a/test/julia/tensor_test.jl b/test/julia/tensor_test.jl
index 844c758..3f9812f 100644
--- a/test/julia/tensor_test.jl
+++ b/test/julia/tensor_test.jl
@@ -57,3 +57,35 @@ tensortest(
   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")
+)

From 467e819436bd8351ed66963b32e4cf185326eacf Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Mon, 31 Mar 2025 15:23:03 -0400
Subject: [PATCH 12/24] change everything to int32

---
 test/julia/tensor_test.jl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/julia/tensor_test.jl b/test/julia/tensor_test.jl
index 3f9812f..7671ffa 100644
--- a/test/julia/tensor_test.jl
+++ b/test/julia/tensor_test.jl
@@ -23,7 +23,7 @@ end
 
 tensortest(
   Tensor(
-    Dense(SparseList(SparseList(Element(0.0)))),
+    Dense(SparseList{Int32}(SparseList{Int32}(Element{0.0,Float64,Int32}()))),
     fsprand(10, 10, 10, 0.1)
   ),
   joinpath(test_files, "input1.bsp.h5"),
@@ -42,7 +42,7 @@ tensortest(
 
 tensortest(
   Tensor(
-    Dense(Dense(Dense(Element(0.0)))),
+    Dense(Dense(Dense(Element{0.0,Float64,Int32}()))),
     fsprand(10, 10, 10, 0.1)
   ),
   joinpath(test_files, "input3.bsp.h5"),

From 054253842acf88612ceb24516a4e0310da2556c4 Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Mon, 7 Apr 2025 11:59:09 -0400
Subject: [PATCH 13/24] make ready for pr

---
 CMakeLists.txt    | 44 --------------------------------------------
 dev.sh            | 35 -----------------------------------
 src/read_matrix.c |  1 -
 3 files changed, 80 deletions(-)
 delete mode 100644 CMakeLists.txt
 delete mode 100755 dev.sh

diff --git a/CMakeLists.txt b/CMakeLists.txt
deleted file mode 100644
index 231146d..0000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-# SPDX-FileCopyrightText: 2024 Binsparse Developers
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-cmake_minimum_required(VERSION 3.5)
-project(binsparse-rc)
-
-cmake_policy(SET CMP0079 NEW)
-
-set(CMAKE_C_STANDARD 11)
-
-set(CMAKE_CXX_STANDARD 20)
-
-set(CMAKE_C_FLAGS "-O3 -march=native")
-
-add_library(binsparse-rc SHARED)
-
-add_subdirectory(include)
-add_subdirectory(src)
-
-# NOTE: For now, both HDF5 and cJSON are `PUBLIC`, meaning that anything that
-# depends on `binsparse-rc` will also link/include HDF5 and cJSON. We can change
-# these to `PRIVATE` to use them only when building binsparse-rc.
-
-find_package(HDF5 REQUIRED COMPONENTS C)
-target_link_libraries(binsparse-rc PUBLIC ${HDF5_C_LIBRARIES})
-target_include_directories(binsparse-rc PUBLIC . ${HDF5_INCLUDE_DIRS})
-
-include(FetchContent)
-FetchContent_Declare(
-  cJSON
-  GIT_REPOSITORY https://github.com/DaveGamble/cJSON.git
-  GIT_TAG v1.7.17
-)
-FetchContent_MakeAvailable(cJSON)
-
-configure_file(${cJSON_SOURCE_DIR}/cJSON.h ${CMAKE_BINARY_DIR}/include/cJSON/cJSON.h)
-target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_BINARY_DIR}/include)
-target_link_libraries(${PROJECT_NAME} PUBLIC cjson)
-
-if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
-  add_subdirectory(examples)
-  add_subdirectory(test)
-endif()
diff --git a/dev.sh b/dev.sh
deleted file mode 100755
index a86d79b..0000000
--- a/dev.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env sh
-
-CONTAINER='binsparse-reference-c'
-IMAGE='docker.io/junikimm717/nvim2025:finch'
-DIR="$(realpath "$(dirname "$0")")"
-
-case "$1" in
-  pull|p)
-    podman pull "$IMAGE"
-    (podman container ls | grep "$CONTAINER" > /dev/null 2>&1) && podman container rm -fv "$CONTAINER"
-    ;;
-  clear|c)
-    (podman container ls -a | grep "$CONTAINER" > /dev/null 2>&1)\
-    && {
-      podman container kill "$CONTAINER" > /dev/null 2>&1;
-      podman container rm "$CONTAINER"
-    }
-    ;;
-  *)
-    set +x
-    if ! (podman container ls -a | grep "$CONTAINER" > /dev/null 2>&1); then
-      podman run\
-        -dt\
-        --name "$CONTAINER"\
-        --group-add keep-groups\
-        -v "$DIR:/workspace"\
-        --privileged\
-        --rm\
-        "$IMAGE"
-    fi || exit 1
-    podman exec\
-      -e ENV=/root/.profile\
-      -it "$CONTAINER" /bin/bash
-    ;;
-esac
diff --git a/src/read_matrix.c b/src/read_matrix.c
index bf2b96d..464210c 100644
--- a/src/read_matrix.c
+++ b/src/read_matrix.c
@@ -143,7 +143,6 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) {
 
   bsp_matrix_format_t format = bsp_get_matrix_format(format_string);
 
-  // isn't this never true?
   assert(format != 0);
 
   matrix.format = format;

From 25126850e4d6da8b2d8f689c1fa8c66dff15c521 Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Mon, 7 Apr 2025 13:33:58 -0700
Subject: [PATCH 14/24] Fix formatting

---
 examples/tensor_test.c           | 5 +++--
 include/binsparse/tensor.h       | 6 ++++--
 include/binsparse/write_tensor.h | 2 +-
 3 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/examples/tensor_test.c b/examples/tensor_test.c
index bfa0bfc..cd30060 100644
--- a/examples/tensor_test.c
+++ b/examples/tensor_test.c
@@ -1,10 +1,11 @@
-#include <binsparse/tensor.h>
 #include <binsparse/read_tensor.h>
+#include <binsparse/tensor.h>
 #include <binsparse/write_tensor.h>
 
 int main(int argc, char** argv) {
   if (argc < 3) {
-    fprintf(stderr, "usage: ./tensor_test [file_name.h5] [output_file_name.h5]\n");
+    fprintf(stderr,
+            "usage: ./tensor_test [file_name.h5] [output_file_name.h5]\n");
     return 1;
   }
   char* file_name = argv[1];
diff --git a/include/binsparse/tensor.h b/include/binsparse/tensor.h
index 3909318..8201e67 100644
--- a/include/binsparse/tensor.h
+++ b/include/binsparse/tensor.h
@@ -123,6 +123,8 @@ static bsp_array_t bsp_get_tensor_values(bsp_tensor_t tensor) {
 
 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);
+  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
index 64497c2..27e1f29 100644
--- a/include/binsparse/write_tensor.h
+++ b/include/binsparse/write_tensor.h
@@ -12,7 +12,7 @@ extern "C" {
 
 // TODO: make cJSON optional.
 
-#include<binsparse/tensor.h>
+#include <binsparse/tensor.h>
 #include <cJSON/cJSON.h>
 
 #ifdef BSP_USE_HDF5

From 5ac75e6da0696f341b71e8d2cb49459df8087deb Mon Sep 17 00:00:00 2001
From: Juni Kim <junikimm717@gmail.com>
Date: Sun, 20 Apr 2025 17:14:52 -0400
Subject: [PATCH 15/24] add cmakelists back in

---
 CMakeLists.txt | 44 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)
 create mode 100644 CMakeLists.txt

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..231146d
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,44 @@
+# SPDX-FileCopyrightText: 2024 Binsparse Developers
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.5)
+project(binsparse-rc)
+
+cmake_policy(SET CMP0079 NEW)
+
+set(CMAKE_C_STANDARD 11)
+
+set(CMAKE_CXX_STANDARD 20)
+
+set(CMAKE_C_FLAGS "-O3 -march=native")
+
+add_library(binsparse-rc SHARED)
+
+add_subdirectory(include)
+add_subdirectory(src)
+
+# NOTE: For now, both HDF5 and cJSON are `PUBLIC`, meaning that anything that
+# depends on `binsparse-rc` will also link/include HDF5 and cJSON. We can change
+# these to `PRIVATE` to use them only when building binsparse-rc.
+
+find_package(HDF5 REQUIRED COMPONENTS C)
+target_link_libraries(binsparse-rc PUBLIC ${HDF5_C_LIBRARIES})
+target_include_directories(binsparse-rc PUBLIC . ${HDF5_INCLUDE_DIRS})
+
+include(FetchContent)
+FetchContent_Declare(
+  cJSON
+  GIT_REPOSITORY https://github.com/DaveGamble/cJSON.git
+  GIT_TAG v1.7.17
+)
+FetchContent_MakeAvailable(cJSON)
+
+configure_file(${cJSON_SOURCE_DIR}/cJSON.h ${CMAKE_BINARY_DIR}/include/cJSON/cJSON.h)
+target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_BINARY_DIR}/include)
+target_link_libraries(${PROJECT_NAME} PUBLIC cjson)
+
+if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
+  add_subdirectory(examples)
+  add_subdirectory(test)
+endif()

From 093add15ee37969643a0b4218f68a2692e1b16c9 Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Wed, 6 Aug 2025 15:29:08 -0700
Subject: [PATCH 16/24] Add SPDX headers, remove `compile_flags.txt`.  (Use
 `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate automatically.)

---
 .gitignore                      |  1 +
 CMakeLists.txt                  | 10 +++++-----
 compile_flags.txt               |  4 ----
 examples/cpp/tensor_test.cpp    |  6 ++++++
 examples/tensor_test.c          |  6 ++++++
 include/binsparse/read_tensor.h |  6 ++++++
 include/binsparse/tensor.h      |  6 ++++++
 src/CMakeLists.txt              |  4 ++--
 src/write_tensor.c              |  6 ++++++
 test/julia/tensor_test.jl       |  4 ++++
 10 files changed, 42 insertions(+), 11 deletions(-)
 delete mode 100644 compile_flags.txt

diff --git a/.gitignore b/.gitignore
index 8fc2fe2..8dddad7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,6 @@
 scripts
 venv
 build*
+compile_flags.txt
 ._*
 tensor_test_files
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 349bbe8..6ea7531 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,11 +46,6 @@ target_include_directories(${PROJECT_NAME}
         $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
         ${HDF5_INCLUDE_DIRS})
 
-if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
-  add_subdirectory(examples)
-  add_subdirectory(test)
-endif()
-
 # Installation rules - these are always needed when the library is built
 install(TARGETS binsparse
         EXPORT binsparse-targets
@@ -87,3 +82,8 @@ install(FILES
     "${CMAKE_CURRENT_BINARY_DIR}/binsparse-config.cmake"
     "${CMAKE_CURRENT_BINARY_DIR}/binsparse-config-version.cmake"
     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/binsparse)
+
+if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
+  add_subdirectory(examples)
+  add_subdirectory(test)
+endif()
diff --git a/compile_flags.txt b/compile_flags.txt
deleted file mode 100644
index ba24856..0000000
--- a/compile_flags.txt
+++ /dev/null
@@ -1,4 +0,0 @@
--I./include
--I./build/include
--DBSP_USE_HDF5
--I/usr/include/hdf5/serial
diff --git a/examples/cpp/tensor_test.cpp b/examples/cpp/tensor_test.cpp
index 677d71e..893c334 100644
--- a/examples/cpp/tensor_test.cpp
+++ b/examples/cpp/tensor_test.cpp
@@ -1 +1,7 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Binsparse Developers
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
 #include "../tensor_test.c"
diff --git a/examples/tensor_test.c b/examples/tensor_test.c
index cd30060..5d3ba6e 100644
--- a/examples/tensor_test.c
+++ b/examples/tensor_test.c
@@ -1,3 +1,9 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Binsparse Developers
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
 #include <binsparse/read_tensor.h>
 #include <binsparse/tensor.h>
 #include <binsparse/write_tensor.h>
diff --git a/include/binsparse/read_tensor.h b/include/binsparse/read_tensor.h
index 9d419ec..579c5c7 100644
--- a/include/binsparse/read_tensor.h
+++ b/include/binsparse/read_tensor.h
@@ -1,3 +1,9 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Binsparse Developers
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
 #pragma once
 
 #ifdef __cplusplus
diff --git a/include/binsparse/tensor.h b/include/binsparse/tensor.h
index 8201e67..74863b2 100644
--- a/include/binsparse/tensor.h
+++ b/include/binsparse/tensor.h
@@ -1,3 +1,9 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Binsparse Developers
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
 #pragma once
 
 #include <binsparse/array.h>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ee41ab5..0b82bd2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,7 +4,7 @@
 
 target_sources(binsparse PRIVATE
   read_matrix.c
-  src/read_tensor.c
+  read_tensor.c
   write_matrix.c
-  src/write_tensor.c
+  write_tensor.c
 )
diff --git a/src/write_tensor.c b/src/write_tensor.c
index 52a4877..11a84ef 100644
--- a/src/write_tensor.c
+++ b/src/write_tensor.c
@@ -1,3 +1,9 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Binsparse Developers
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
 #include <assert.h>
 #include <binsparse/tensor.h>
 #include <unistd.h>
diff --git a/test/julia/tensor_test.jl b/test/julia/tensor_test.jl
index 7671ffa..cbba45f 100644
--- a/test/julia/tensor_test.jl
+++ b/test/julia/tensor_test.jl
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2024 Binsparse Developers
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
 using Finch;
 using HDF5;
 

From 0e75d8fb729c351ef28f8097a8c28175e94d1c44 Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Wed, 6 Aug 2025 15:35:10 -0700
Subject: [PATCH 17/24] Add missing includes.

---
 include/binsparse/read_tensor.h | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/include/binsparse/read_tensor.h b/include/binsparse/read_tensor.h
index 579c5c7..bcf3145 100644
--- a/include/binsparse/read_tensor.h
+++ b/include/binsparse/read_tensor.h
@@ -6,6 +6,14 @@
 
 #pragma once
 
+#include <binsparse/tensor.h>
+
+#ifndef __cplusplus
+#include <stddef.h>
+#else
+#include <cstddef>
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif

From 35435891beaf99d725b3e90175cf78488acffbc9 Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Wed, 6 Aug 2025 16:00:36 -0700
Subject: [PATCH 18/24] Add Tensor tests.

---
 .github/workflows/ci.yml | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 365a69d..0ae2e73 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,8 +38,14 @@ jobs:
         cmake -B build
     - name: Build
       run: VERBOSE=true make -C build -j `nproc`
-    - name: Test
+    - name: Test-Matrix
       run: ctest --test-dir ./build/test/bash
+    - name: Install-Julia
+      run: curl -fsSL https://install.julialang.org | sh
+    - name: Install-Finch
+      run: julia -e 'using Pkg; Pkg.add("Finch")'
+    - name: Test-Tensor
+      run: ctest --test-dir ./build/test/julia
 
   clang:
     runs-on: 'ubuntu-latest'
@@ -55,5 +61,11 @@ jobs:
         cmake -B build
     - name: Build
       run: VERBOSE=true make -C build -j `nproc`
-    - name: Test
+    - name: Test-Matrix
       run: ctest --test-dir ./build/test/bash
+    - name: Install-Julia
+      run: curl -fsSL https://install.julialang.org | sh
+    - name: Install-Finch
+      run: julia -e 'using Pkg; Pkg.add("Finch")'
+    - name: Test-Tensor
+      run: ctest --test-dir ./build/test/julia

From 3abac30bd130f1e9eeabde66f374262c989de958 Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Wed, 6 Aug 2025 16:15:22 -0700
Subject: [PATCH 19/24] Fix Julia install

---
 .github/workflows/ci.yml | 41 +++++++++++++---------------------------
 1 file changed, 13 insertions(+), 28 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0ae2e73..ef207c3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,12 +23,20 @@ jobs:
     - run: pip install -r requirements.txt
     - name: Checks
       uses: pre-commit/action@v3.0.0
-
-  gcc:
+  
+  build:
     runs-on: 'ubuntu-latest'
+    strategy:
+      matrix:
+        include:
+          - cc:  gcc-12
+            cxx: g++-12
+          - cc:  clang
+            cxx: clang++
+    name: ${{ matrix.cc }}
     env:
-      CXX: g++-12
-      CC: gcc-12
+      CC: ${{ matrix.cc }}
+      CXX: ${{ matrix.cxx }}
     steps:
     - uses: actions/checkout@v4
     - name: CMake
@@ -41,30 +49,7 @@ jobs:
     - name: Test-Matrix
       run: ctest --test-dir ./build/test/bash
     - name: Install-Julia
-      run: curl -fsSL https://install.julialang.org | sh
-    - name: Install-Finch
-      run: julia -e 'using Pkg; Pkg.add("Finch")'
-    - name: Test-Tensor
-      run: ctest --test-dir ./build/test/julia
-
-  clang:
-    runs-on: 'ubuntu-latest'
-    env:
-      CXX: clang++
-      CC: clang
-    steps:
-    - uses: actions/checkout@v4
-    - name: CMake
-      run: |
-        sudo apt-get update
-        sudo apt-get install libhdf5-dev clang
-        cmake -B build
-    - name: Build
-      run: VERBOSE=true make -C build -j `nproc`
-    - name: Test-Matrix
-      run: ctest --test-dir ./build/test/bash
-    - name: Install-Julia
-      run: curl -fsSL https://install.julialang.org | sh
+      run: curl -fsSL https://install.julialang.org | sh -s -- -y
     - name: Install-Finch
       run: julia -e 'using Pkg; Pkg.add("Finch")'
     - name: Test-Tensor

From 53f2a76573681ce0f31298d674a47e3c68dbad50 Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Wed, 6 Aug 2025 16:18:10 -0700
Subject: [PATCH 20/24] Fix whitespace

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ef207c3..86769f5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,7 +23,7 @@ jobs:
     - run: pip install -r requirements.txt
     - name: Checks
       uses: pre-commit/action@v3.0.0
-  
+
   build:
     runs-on: 'ubuntu-latest'
     strategy:

From 79b7f955a96c9a837440a2b18c996dfd703ab939 Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Wed, 6 Aug 2025 16:33:18 -0700
Subject: [PATCH 21/24] Turn off Julia pre-compilation, install HDF5.

---
 .github/workflows/ci.yml | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 86769f5..938bf93 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,15 +42,17 @@ jobs:
     - name: CMake
       run: |
         sudo apt-get update
-        sudo apt-get install libhdf5-dev $CXX $CC
+        sudo apt-get install libhdf5-dev gcc-12 g++-12 clang
         cmake -B build
     - name: Build
       run: VERBOSE=true make -C build -j `nproc`
-    - name: Test-Matrix
+    - name: Matrix Tests
       run: ctest --test-dir ./build/test/bash
-    - name: Install-Julia
+    - name: Install Julia
       run: curl -fsSL https://install.julialang.org | sh -s -- -y
-    - name: Install-Finch
-      run: julia -e 'using Pkg; Pkg.add("Finch")'
-    - name: Test-Tensor
+    - name: Disable Julia precompilation
+      run: julia -e 'using PrecompileTools, Preferences; set_preferences!(PrecompileTools, "precompile_workloads" => false; force=true)'
+    - name: Install Julia Packages
+      run: julia -e 'using Pkg; Pkg.add(["Finch", "HDF5"])'
+    - name: Tensor Tests
       run: ctest --test-dir ./build/test/julia

From 8980791a1316684eb6b5fda850ee7efa6e5b0df6 Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Wed, 6 Aug 2025 16:39:49 -0700
Subject: [PATCH 22/24] Add missing package.

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 938bf93..3d8680c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -51,7 +51,7 @@ jobs:
     - name: Install Julia
       run: curl -fsSL https://install.julialang.org | sh -s -- -y
     - name: Disable Julia precompilation
-      run: julia -e 'using PrecompileTools, Preferences; set_preferences!(PrecompileTools, "precompile_workloads" => false; force=true)'
+      run: julia -e 'using Pkg; Pkg.add("PrecompileTools"); using PrecompileTools, Preferences; set_preferences!(PrecompileTools, "precompile_workloads" => false; force=true)'
     - name: Install Julia Packages
       run: julia -e 'using Pkg; Pkg.add(["Finch", "HDF5"])'
     - name: Tensor Tests

From 0c3f3f83b8d1afbe14a4556014262d1f06e94ef4 Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Wed, 6 Aug 2025 16:41:42 -0700
Subject: [PATCH 23/24] Add missing Julia package.

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3d8680c..516614f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -51,7 +51,7 @@ jobs:
     - name: Install Julia
       run: curl -fsSL https://install.julialang.org | sh -s -- -y
     - name: Disable Julia precompilation
-      run: julia -e 'using Pkg; Pkg.add("PrecompileTools"); using PrecompileTools, Preferences; set_preferences!(PrecompileTools, "precompile_workloads" => false; force=true)'
+      run: julia -e 'using Pkg; Pkg.add(["PrecompileTools", "Preferences"]); using PrecompileTools, Preferences; set_preferences!(PrecompileTools, "precompile_workloads" => false; force=true)'
     - name: Install Julia Packages
       run: julia -e 'using Pkg; Pkg.add(["Finch", "HDF5"])'
     - name: Tensor Tests

From e2c21a0cd59a5c9ac46a2057418cd4835a54d71a Mon Sep 17 00:00:00 2001
From: Benjamin Brock <benjamin.brock@intel.com>
Date: Wed, 6 Aug 2025 17:44:51 -0700
Subject: [PATCH 24/24] Correct comment.

---
 include/binsparse/tensor.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/include/binsparse/tensor.h b/include/binsparse/tensor.h
index 74863b2..4d63512 100644
--- a/include/binsparse/tensor.h
+++ b/include/binsparse/tensor.h
@@ -26,7 +26,7 @@ typedef struct {
   bsp_array_t values;
 } bsp_element_t;
 
-// corresponds to BSP_TENSOR_DENSE
+// corresponds to BSP_TENSOR_SPARSE
 typedef struct {
   int rank;
   // pointers_to, while it will only ever point to one bsp_array_t, must be kept
@@ -38,6 +38,7 @@ typedef struct {
   bsp_level_t* child;
 } bsp_sparse_t;
 
+// corresponds to BSP_TENSOR_DENSE
 typedef struct {
   int rank;
   bsp_level_t* child;