New Sign in

utensil

Public
fd325f6ddbcf554e3db42ec80091f258296a0b81
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d6beb5d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+CFLAGS ?= -std=c99 -Wall -Wextra -g
+
+all:
+	$(CC) $(CFLAGS) test.c -o test
+	./test
+
+.PHONY: all
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..ba64202
--- /dev/null
+++ b/test.c
@@ -0,0 +1,45 @@
+#include <assert.h>
+
+#include "utensil.h"
+
+static void test_shape(void) {
+  ut_shape s = ut_shape_new(3, (int[]){2, 3, 4});
+  assert(s.nelem == 24);
+  assert(s.ndim == 3);
+  // element [1][2][3] should be at offset 1*12 + 2*4 + 3 = 23
+  assert(ut_index(s, (int[]){1, 2, 3}) == 23);
+  // element [0][0][0] is always 0
+  assert(ut_index(s, (int[]){0, 0, 0}) == 0);
+}
+
+static void test_lifetime(void) {
+  ut_tensor* a = ut_alloc(2, (int[]){4, 4});
+  a->data[0] = 42.f;
+
+  ut_tensor* v = ut_view(a, 1, (int[]){16});
+
+  assert(v->data[0] == 42.f);
+  assert(v->owner == a);
+
+  v->data[5] = 99.f;
+  assert(a->data[5] == 99.f);
+
+  ut_free(v);
+  assert(a->data[0] == 42.f);
+  ut_free(a);
+}
+
+void test_reshape(void) {
+  ut_tensor* a = ut_randn(2, (int[]){2, 3}, 0.f, 1.f);
+  ut_tensor* v = ut_view(a, 1, (int[]){6});
+  for (int i = 0; i < 6; i++) assert(v->data[i] == a->data[i]);
+  ut_free(v);
+  ut_free(a);
+}
+
+int main() {
+  test_shape();
+  test_lifetime();
+  test_reshape();
+  return 0;
+}
diff --git a/utensil.h b/utensil.h
new file mode 100644
index 0000000..f893754
--- /dev/null
+++ b/utensil.h
@@ -0,0 +1,82 @@
+#ifndef UTENSIL_H
+#define UTENSIL_H
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UT_MAX_DIMS 4
+
+typedef struct ut_shape {
+  int ndim;
+  int nelem;
+  int shape[UT_MAX_DIMS];
+  int stride[UT_MAX_DIMS];
+} ut_shape;
+
+typedef struct ut_tensor {
+  ut_shape shape;
+  float* data;
+  int rc;
+  struct ut_tensor* owner;
+} ut_tensor;
+
+ut_shape ut_shape_new(int ndim, const int* dim) {
+  ut_shape s = {.ndim = ndim, .nelem = 1};
+  for (int i = 0; i < ndim; i++) s.shape[i] = dim[i], s.nelem *= dim[i];
+  return s;
+}
+
+int ut_index(ut_shape s, const int* idx) {
+  int flat = 0, stride = 1;
+  for (int i = s.ndim - 1; i >= 0; i--) flat += idx[i] * stride, stride *= s.shape[i];
+  return flat;
+}
+
+ut_tensor* ut_alloc(int ndim, int* dim) {
+  ut_tensor* t = (ut_tensor*)malloc(sizeof(ut_tensor));
+  *t = (struct ut_tensor){.shape = ut_shape_new(ndim, dim), .owner = NULL, .rc = 1};
+  t->data = (float*)malloc(t->shape.nelem * sizeof(float));
+  return t;
+}
+
+ut_tensor* ut_randn(int ndim, int* dim, float mean, float stddev) {
+  ut_tensor* t = ut_alloc(ndim, dim);
+  for (int i = 0; i < t->shape.nelem; i++) {
+    // Box-Muller transform to generate normally distributed random numbers
+    float u1 = (float)rand() / RAND_MAX;
+    float u2 = (float)rand() / RAND_MAX;
+    float z0 = sqrtf(-2.0f * logf(u1)) * cosf(6.28f * u2);
+    t->data[i] = z0 * stddev + mean;
+  }
+  return t;
+}
+
+ut_tensor* ut_from_data(int ndim, const int* dim, const float* data) {
+  ut_tensor* t = ut_alloc(ndim, dim);
+  memcpy(t->data, data, (size_t)t->shape.nelem * sizeof(float));
+  return t;
+}
+
+ut_tensor* ut_clone(ut_tensor* t) {
+  ut_tensor* c = ut_alloc(t->shape.ndim, t->shape.shape);
+  memcpy(c->data, t->data, (size_t)t->shape.nelem * sizeof(float));
+  return c;
+}
+
+void ut_free(ut_tensor* t) {
+  if (!--t->rc) return;
+  if (!t->owner) free(t->data);
+  free(t);
+}
+
+ut_tensor* ut_view(ut_tensor* t, int ndim, const int* dim) {
+  ut_shape ns = ut_shape_new(ndim, dim);
+  ut_tensor* v = (ut_tensor*)malloc(sizeof(ut_tensor));
+  *v = (struct ut_tensor){.shape = ns, .data = t->data, .owner = t->owner ? t->owner : t};
+  return v;
+}
+
+ut_tensor* ut_retain(ut_tensor* t) { return t->rc++, t; }
+
+#endif  // UTENSIL_H