From 0363612ba3c7802b5c83e3168022d2f642f4570e Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Tue, 1 Jul 2025 21:12:27 +0530 Subject: [PATCH 01/21] Add autograd support for min and max tensor operations --- src/operator.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/src/operator.c b/src/operator.c index bd3a94b..dd3e7d7 100644 --- a/src/operator.c +++ b/src/operator.c @@ -421,5 +421,115 @@ Tensor Tensor_sub(Tensor self, Tensor other) { res.node->n_inputs = 2; res.node->name = "Sub"; } + return res; +} + +static Tensor GradFn_max(Tensor self, int i) { + // f(x) = max(x); f'(x) = 1 for elements equal to max, 0 otherwise + Tensor input = self.node->inputs[i]; + Tensor res = Tensor_new(input.shape, false); + float max_val = self.data->flex[0]; + + for (int j = 0; j < res.data->numel; j++) { + res.data->flex[j] = 0.0f; + } + + int max_count = 0; + for (int j = 0; j < input.data->numel; j++) { + if (input.data->flex[j] == max_val) { + max_count++; + } + } + + float grad_value = 1.0f / max_count; + for (int j = 0; j < input.data->numel; j++) { + if (input.data->flex[j] == max_val) { + res.data->flex[j] = grad_value; + } + } + + return res; +} + +Tensor Tensor_max(Tensor self) { + if (self.data->numel == 0){ + fprintf(stderr, "Error: max() on an empty tensor.\n"); + abort(); + } + bool requires_grad = !cten_is_eval() && (self.node != NULL); + Tensor res = Tensor_new((TensorShape){1, 0, 0, 0}, requires_grad); + + // Find maximum value + float max_val = self.data->flex[0]; + for (int i = 1; i < self.data->numel; i++) { + if (self.data->flex[i] > max_val) { + max_val = self.data->flex[i]; + } + } + + res.data->flex[0] = max_val; + + if (requires_grad) { + res.node->grad_fn = GradFn_max; + res.node->inputs[0] = self; + res.node->n_inputs = 1; + res.node->name = "Max"; + } + + return res; +} + +static Tensor GradFn_min(Tensor self, int i) { + // f(x) = min(x); f'(x) = 1 for elements equal to min, 0 otherwise + Tensor input = self.node->inputs[i]; + Tensor res = Tensor_new(input.shape, false); + float min_val = self.data->flex[0]; + + for (int j = 0; j < res.data->numel; j++) { + res.data->flex[j] = 0.0f; + } + + int min_count = 0; + for (int j = 0; j < input.data->numel; j++) { + if (input.data->flex[j] == min_val) { + min_count++; + } + } + + float grad_value = 1.0f / min_count; + for (int j = 0; j < input.data->numel; j++) { + if (input.data->flex[j] == min_val) { + res.data->flex[j] = grad_value; + } + } + + return res; +} + +Tensor Tensor_min(Tensor self) { + if (self.data->numel == 0){ + fprintf(stderr, "Error: min() on an empty tensor.\n"); + abort(); + } + bool requires_grad = !cten_is_eval() && (self.node != NULL); + Tensor res = Tensor_new((TensorShape){1, 0, 0, 0}, requires_grad); + + // Find minimum value + float min_val = self.data->flex[0]; + for (int i = 1; i < self.data->numel; i++) { + if (self.data->flex[i] < min_val) { + min_val = self.data->flex[i]; + } + } + + res.data->flex[0] = min_val; + + if (requires_grad) { + res.node->grad_fn = GradFn_min; + res.node->inputs[0] = self; + res.node->n_inputs = 1; + res.node->name = "Min"; + } + return res; } \ No newline at end of file From 28df5d26c1f99fb4df4a22f00b42e493a0f72f0f Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Tue, 1 Jul 2025 21:24:10 +0530 Subject: [PATCH 02/21] test cases for test_max and test_min added --- tests/Operator/test_max.c | 97 +++++++++++++++++++++++++++++++++++++++ tests/Operator/test_min.c | 96 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 tests/Operator/test_max.c create mode 100644 tests/Operator/test_min.c diff --git a/tests/Operator/test_max.c b/tests/Operator/test_max.c new file mode 100644 index 0000000..cfbd40a --- /dev/null +++ b/tests/Operator/test_max.c @@ -0,0 +1,97 @@ +#include "../../include/cten.h" +#include "../test_utils.h" +#include "../csv_reporter.h" +#include "../test_config.h" +#include + +void test_max_operator() { + const char* op_name = "max"; + PoolId pool_id = 8; // Unique pool ID for max operator tests + + cten_begin_malloc(pool_id); + + // Test Case 1: Max of a scalar tensor + { + const char* tc_name = "max_scalar"; + TensorShape s_shape = {1}; + float d1[] = {5.0f}; + float exp_d[] = {5.0f}; + Tensor t1 = create_test_tensor(s_shape, d1, false); + Tensor expected_res = create_test_tensor(s_shape, exp_d, false); + Tensor actual_res = Tensor_max(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 2: Max of a vector tensor + { + const char* tc_name = "max_vector"; + TensorShape v_shape = {5}; + float d1[] = {1.0f, 7.0f, 3.0f, 5.0f, 2.0f}; + float exp_d[] = {7.0f}; // Max is 7 + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(v_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_max(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 3: Max of a matrix tensor + { + const char* tc_name = "max_matrix"; + TensorShape m_shape = {2, 3}; + float d1[] = {1.0f, 2.0f, 8.0f, 3.0f, 4.0f, 6.0f}; + float exp_d[] = {8.0f}; // Max is 8 + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(m_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_max(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 4: Max of a tensor with negative numbers + { + const char* tc_name = "max_vector_negative"; + TensorShape v_shape = {4}; + float d1[] = {-1.0f, -2.0f, -3.0f, -0.5f}; + float exp_d[] = {-0.5f}; // Max is -0.5 + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(v_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_max(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 5: Max with duplicate maximum values + { + const char* tc_name = "max_duplicate"; + TensorShape v_shape = {5}; + float d1[] = {1.0f, 9.0f, 3.0f, 9.0f, 2.0f}; + float exp_d[] = {9.0f}; // Max is 9, appears twice + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(v_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_max(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 6: Max of a 3D tensor + { + const char* tc_name = "max_3d_tensor"; + TensorShape t_shape = {2, 2, 2}; + float d1[] = {1.0f, 2.0f, 3.0f, 12.0f, 5.0f, 6.0f, 7.0f, 8.0f}; + float exp_d[] = {12.0f}; // Max is 12 + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(t_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_max(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + cten_free(pool_id); +} diff --git a/tests/Operator/test_min.c b/tests/Operator/test_min.c new file mode 100644 index 0000000..e54a6da --- /dev/null +++ b/tests/Operator/test_min.c @@ -0,0 +1,96 @@ +#include "../../include/cten.h" +#include "../test_utils.h" +#include "../csv_reporter.h" +#include "../test_config.h" +#include + +void test_min_operator() { + const char* op_name = "min"; + PoolId pool_id = 9; // Unique pool ID for min operator tests + + cten_begin_malloc(pool_id); + + // Test Case 1: Min of a scalar tensor + { + const char* tc_name = "min_scalar"; + TensorShape s_shape = {1}; + float d1[] = {5.0f}; + float exp_d[] = {5.0f}; + Tensor t1 = create_test_tensor(s_shape, d1, false); + Tensor expected_res = create_test_tensor(s_shape, exp_d, false); + Tensor actual_res = Tensor_min(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 2: Min of a vector tensor + { + const char* tc_name = "min_vector"; + TensorShape v_shape = {5}; + float d1[] = {8.0f, 3.0f, 7.0f, 5.0f, 9.0f}; + float exp_d[] = {3.0f}; // Min is 3 + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(v_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_min(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 3: Min of a matrix tensor + { + const char* tc_name = "min_matrix"; + TensorShape m_shape = {2, 3}; + float d1[] = {5.0f, 2.0f, 8.0f, 3.0f, 4.0f, 6.0f}; + float exp_d[] = {2.0f}; // Min is 2 + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(m_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_min(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 4: Min of a tensor with negative numbers + { + const char* tc_name = "min_vector_negative"; + TensorShape v_shape = {4}; + float d1[] = {-1.0f, -2.0f, -3.0f, -0.5f}; + float exp_d[] = {-3.0f}; // Min is -3.0 + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(v_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_min(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 5: Min with duplicate minimum values + { + const char* tc_name = "min_duplicate"; + TensorShape v_shape = {5}; + float d1[] = {5.0f, 2.0f, 8.0f, 2.0f, 7.0f}; + float exp_d[] = {2.0f}; // Min is 2, appears twice + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(v_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_min(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 6: Min of a 3D tensor + { + const char* tc_name = "min_3d_tensor"; + TensorShape t_shape = {2, 2, 2}; + float d1[] = {10.0f, 20.0f, 5.0f, 12.0f, 15.0f, 16.0f, 7.0f, 18.0f}; + float exp_d[] = {5.0f}; // Min is 5 + TensorShape exp_shape = {1, 0, 0, 0}; + Tensor t1 = create_test_tensor(t_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor actual_res = Tensor_min(t1); + + compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + cten_free(pool_id); +} From 81229c58e08a19c7e88986cf7a13684cbc1467ea Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Tue, 1 Jul 2025 21:24:47 +0530 Subject: [PATCH 03/21] test_max_operator and test_min_operator added in cten_tests --- tests/cten_tests.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/cten_tests.c b/tests/cten_tests.c index 7c8cd1e..c9644e7 100644 --- a/tests/cten_tests.c +++ b/tests/cten_tests.c @@ -25,6 +25,8 @@ void test_pow_operator(); void test_reciprocal_operator(); void test_square_operator(); void test_div_operator(); +void test_max_operator(); +void test_min_operator(); // Backward tests void test_add_backward(); @@ -96,6 +98,12 @@ int main() { test_div_operator(); printf("Div operator tests finished.\n"); + test_max_operator(); + printf("Max operator tests finished.\n"); + + test_min_operator(); + printf("Min operator tests finished.\n"); + // Backward tests test_add_backward(); printf("Add backward tests finished.\n"); From 6e0100de75ac5ca01c630b159e2625502745dd7c Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Tue, 1 Jul 2025 21:25:24 +0530 Subject: [PATCH 04/21] change fprintf to cten_assert which handle these cases safely --- src/operator.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/operator.c b/src/operator.c index dd3e7d7..c8ea399 100644 --- a/src/operator.c +++ b/src/operator.c @@ -453,8 +453,7 @@ static Tensor GradFn_max(Tensor self, int i) { Tensor Tensor_max(Tensor self) { if (self.data->numel == 0){ - fprintf(stderr, "Error: max() on an empty tensor.\n"); - abort(); + cten_assert(false, "Error: max() on an empty tensor."); } bool requires_grad = !cten_is_eval() && (self.node != NULL); Tensor res = Tensor_new((TensorShape){1, 0, 0, 0}, requires_grad); @@ -508,8 +507,7 @@ static Tensor GradFn_min(Tensor self, int i) { Tensor Tensor_min(Tensor self) { if (self.data->numel == 0){ - fprintf(stderr, "Error: min() on an empty tensor.\n"); - abort(); + cten_assert(false, "Error: min() on an empty tensor."); } bool requires_grad = !cten_is_eval() && (self.node != NULL); Tensor res = Tensor_new((TensorShape){1, 0, 0, 0}, requires_grad); From 803cf5539b353fece5c73c88b5e874e812288ff4 Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Tue, 1 Jul 2025 21:41:37 +0530 Subject: [PATCH 05/21] default pool-id --- tests/Operator/test_max.c | 4 ++-- tests/Operator/test_min.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Operator/test_max.c b/tests/Operator/test_max.c index cfbd40a..6ea3639 100644 --- a/tests/Operator/test_max.c +++ b/tests/Operator/test_max.c @@ -6,8 +6,8 @@ void test_max_operator() { const char* op_name = "max"; - PoolId pool_id = 8; // Unique pool ID for max operator tests - + PoolId pool_id = 0; + cten_begin_malloc(pool_id); // Test Case 1: Max of a scalar tensor diff --git a/tests/Operator/test_min.c b/tests/Operator/test_min.c index e54a6da..d7eae4c 100644 --- a/tests/Operator/test_min.c +++ b/tests/Operator/test_min.c @@ -6,7 +6,7 @@ void test_min_operator() { const char* op_name = "min"; - PoolId pool_id = 9; // Unique pool ID for min operator tests + PoolId pool_id = 0; cten_begin_malloc(pool_id); From aecd8ef6d3c7c61ccc37db474a14c0789749676e Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Wed, 2 Jul 2025 13:32:50 +0530 Subject: [PATCH 06/21] backward test also added for Tensor_min and Tensor_max --- tests/Backward/test_max_backward.c | 97 ++++++++++ tests/Backward/test_mean_backward.c | 263 ++++++++++++++++++++++++++++ tests/Operator/test_max.c | 24 +-- tests/Operator/test_min.c | 24 +-- 4 files changed, 384 insertions(+), 24 deletions(-) create mode 100644 tests/Backward/test_max_backward.c create mode 100644 tests/Backward/test_mean_backward.c diff --git a/tests/Backward/test_max_backward.c b/tests/Backward/test_max_backward.c new file mode 100644 index 0000000..973dc19 --- /dev/null +++ b/tests/Backward/test_max_backward.c @@ -0,0 +1,97 @@ +#include "../../include/cten.h" +#include "../test_utils.h" +#include "../csv_reporter.h" +#include "../test_config.h" +#include + +void test_max_backward() { + const char* op_name = "max_backward"; + PoolId pool_id = 0; + cten_begin_malloc(pool_id); + + // Test Case 1: Vector with a unique maximum value + { + const char* tc_name = "max_vector_unique_backward"; + TensorShape v_shape = {3}; + float data[] = {2.0f, 8.0f, 5.0f}; + float exp_grad[] = {0.0f, 1.0f, 0.0f}; + + Tensor t = create_test_tensor(v_shape, data, true); + Tensor z = Tensor_max(t); + + Tensor grad_dummy = {0}; + Tensor_backward(z, grad_dummy); + + Tensor expected_grad = create_test_tensor(v_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 2: Vector with duplicate maximum values + { + const char* tc_name = "max_vector_duplicate_backward"; + TensorShape v_shape = {4}; + float data[] = {9.0f, 3.0f, 9.0f, 1.0f}; + float exp_grad[] = {0.5f, 0.0f, 0.5f, 0.0f}; + + Tensor t = create_test_tensor(v_shape, data, true); + Tensor z = Tensor_max(t); + + Tensor grad_dummy = {0}; + Tensor_backward(z, grad_dummy); + + Tensor expected_grad = create_test_tensor(v_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 3: Matrix with a unique maximum value + { + const char* tc_name = "max_matrix_unique_backward"; + TensorShape m_shape = {2, 2}; + float data[] = {1.0f, 2.0f, 10.0f, 4.0f}; + float exp_grad[] = {0.0f, 0.0f, 1.0f, 0.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_max(t); + + Tensor grad_dummy = {0}; + Tensor_backward(z, grad_dummy); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 4: Complex computation graph (z = max(x) * y) + { + const char* tc_name = "max_complex_graph_backward"; + TensorShape v_shape = {3}; + TensorShape s_shape = {1}; + float x_data[] = {1.0f, 5.0f, 2.0f}; + float y_data[] = {4.0f}; + + // Let m = max(x). z = m * y. + // dz/dx = dz/dm * dm/dx + // dz/dm = y = 4.0 + // dm/dx = [0, 1, 0] + // dz/dx = 4.0 * [0, 1, 0] = [0, 4.0, 0] + float exp_grad_x[] = {0.0f, 4.0f, 0.0f}; + // dz/dy = m = 5.0 + float exp_grad_y[] = {5.0f}; + + Tensor x = create_test_tensor(v_shape, x_data, true); + Tensor y = create_test_tensor(s_shape, y_data, true); + + Tensor m = Tensor_max(x); // m = 5.0 + Tensor z = Tensor_mul(m, y); // z = 20.0 + + Tensor grad_dummy = {0}; + Tensor_backward(z, grad_dummy); + + Tensor expected_grad_x_tensor = create_test_tensor(v_shape, exp_grad_x, false); + Tensor expected_grad_y_tensor = create_test_tensor(s_shape, exp_grad_y, false); + + compare_tensors(&x.node->grad, &expected_grad_x_tensor, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&y.node->grad, &expected_grad_y_tensor, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + + cten_free(pool_id); +} \ No newline at end of file diff --git a/tests/Backward/test_mean_backward.c b/tests/Backward/test_mean_backward.c new file mode 100644 index 0000000..1db9d74 --- /dev/null +++ b/tests/Backward/test_mean_backward.c @@ -0,0 +1,263 @@ +#include "../../include/cten.h" +#include "../test_utils.h" +#include "../csv_reporter.h" +#include "../test_config.h" +#include + +void test_mean_backward() { + const char* op_name = "mean_backward"; + PoolId pool_id = 0; + cten_begin_malloc(pool_id); + + // Test Case 1: Mean all elements backward + { + const char* tc_name = "Mean_all_backward"; + // Sub-test 1: Vector mean all + { + TensorShape v_shape = {3}; + float data[] = {1.0f, 2.0f, 3.0f}; + float exp_grad[] = {1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f}; + + Tensor t = create_test_tensor(v_shape, data, true); + Tensor z = Tensor_mean(t); // mean of all elements + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(v_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: Matrix mean all + { + TensorShape m_shape = {2, 3}; + float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float exp_grad[] = {1.0f/6.0f, 1.0f/6.0f, 1.0f/6.0f, 1.0f/6.0f, 1.0f/6.0f, 1.0f/6.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_mean(t); // mean of all elements + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 3: 3D tensor mean all + { + TensorShape tensor3d_shape = {2, 2, 2}; + float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}; + float exp_grad[] = {1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f}; + + Tensor t = create_test_tensor(tensor3d_shape, data, true); + Tensor z = Tensor_mean(t); // mean of all elements + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 3, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 2: Mean along dimension 0 + { + const char* tc_name = "Mean_dim0_backward"; + // Sub-test 1: Matrix mean along dim 0 + { + TensorShape m_shape = {2, 3}; + float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float exp_grad[] = {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f}; // 1/2 for each element + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_mean(t, 0); // mean along dim 0 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: 3D tensor mean along dim 0 + { + TensorShape tensor3d_shape = {2, 3, 4}; + float data[24]; + for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); + float exp_grad[24]; + for (int i = 0; i < 24; i++) exp_grad[i] = 0.5f; // 1/2 for each element + + Tensor t = create_test_tensor(tensor3d_shape, data, true); + Tensor z = Tensor_mean(t, 0); // mean along dim 0 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 3: Mean along dimension 1 + { + const char* tc_name = "Mean_dim1_backward"; + // Sub-test 1: Matrix mean along dim 1 + { + TensorShape m_shape = {2, 3}; + float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float exp_grad[] = {1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f}; // 1/3 for each element + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_mean(t, 1); // mean along dim 1 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: 3D tensor mean along dim 1 + { + TensorShape tensor3d_shape = {2, 3, 4}; + float data[24]; + for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); + float exp_grad[24]; + for (int i = 0; i < 24; i++) exp_grad[i] = 1.0f/3.0f; // 1/3 for each element + + Tensor t = create_test_tensor(tensor3d_shape, data, true); + Tensor z = Tensor_mean(t, 1); // mean along dim 1 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 4: Mean along dimension 2 + { + const char* tc_name = "Mean_dim2_backward"; + // Sub-test 1: 3D tensor mean along dim 2 + { + TensorShape tensor3d_shape = {2, 3, 4}; + float data[24]; + for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); + float exp_grad[24]; + for (int i = 0; i < 24; i++) exp_grad[i] = 0.25f; // 1/4 for each element + + Tensor t = create_test_tensor(tensor3d_shape, data, true); + Tensor z = Tensor_mean(t, 2); // mean along dim 2 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 5: Random input mean backward + { + const char* tc_name = "Random_input_mean_backward"; + // Sub-test 1: Random matrix mean along dim 0 + { + TensorShape m_shape = {3, 4}; + float data[] = { + 2.5f, 1.3f, 4.8f, 3.2f, + 0.7f, 5.1f, 2.9f, 6.4f, + 3.6f, 1.8f, 4.2f, 0.9f + }; + float exp_grad[] = { + 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, + 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, + 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f + }; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_mean(t, 0); // mean along dim 0 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: Random matrix mean along dim 1 + { + TensorShape m_shape = {3, 4}; + float data[] = { + 2.5f, 1.3f, 4.8f, 3.2f, + 0.7f, 5.1f, 2.9f, 6.4f, + 3.6f, 1.8f, 4.2f, 0.9f + }; + float exp_grad[] = { + 0.25f, 0.25f, 0.25f, 0.25f, + 0.25f, 0.25f, 0.25f, 0.25f, + 0.25f, 0.25f, 0.25f, 0.25f + }; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_mean(t, 1); // mean along dim 1 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 6: Chained operations with mean + { + const char* tc_name = "Chained_operations_with_mean"; + // Sub-test 1: Mean(a*b, dim=0) + { + TensorShape m_shape = {2, 3}; + float a_data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float b_data[] = {0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f}; + float exp_grad_a[] = {0.5f/2.0f, 1.5f/2.0f, 2.5f/2.0f, 3.5f/2.0f, 4.5f/2.0f, 5.5f/2.0f}; + float exp_grad_b[] = {1.0f/2.0f, 2.0f/2.0f, 3.0f/2.0f, 4.0f/2.0f, 5.0f/2.0f, 6.0f/2.0f}; + + Tensor a = create_test_tensor(m_shape, a_data, true); + Tensor b = create_test_tensor(m_shape, b_data, true); + Tensor prod = Tensor_mul(a, b); + Tensor z = Tensor_mean(prod, 0); + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad_a = create_test_tensor(m_shape, exp_grad_a, false); + Tensor expected_grad_b = create_test_tensor(m_shape, exp_grad_b, false); + + compare_tensors(&a.node->grad, &expected_grad_a, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&b.node->grad, &expected_grad_b, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: Mean(a+b, dim=1) + { + TensorShape m_shape = {2, 3}; + float a_data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float b_data[] = {0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f}; + float exp_grad_a[] = {1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f}; + float exp_grad_b[] = {1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f}; + + Tensor a = create_test_tensor(m_shape, a_data, true); + Tensor b = create_test_tensor(m_shape, b_data, true); + Tensor sum_ab = Tensor_add(a, b); + Tensor z = Tensor_mean(sum_ab, 1); + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad_a = create_test_tensor(m_shape, exp_grad_a, false); + Tensor expected_grad_b = create_test_tensor(m_shape, exp_grad_b, false); + + compare_tensors(&a.node->grad, &expected_grad_a, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + compare_tensors(&b.node->grad, &expected_grad_b, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + } + + cten_free(pool_id); +} \ No newline at end of file diff --git a/tests/Operator/test_max.c b/tests/Operator/test_max.c index 6ea3639..3cbb60c 100644 --- a/tests/Operator/test_max.c +++ b/tests/Operator/test_max.c @@ -14,8 +14,8 @@ void test_max_operator() { { const char* tc_name = "max_scalar"; TensorShape s_shape = {1}; - float d1[] = {5.0f}; - float exp_d[] = {5.0f}; + float d1[] = {2.7885f}; + float exp_d[] = {2.7885f}; Tensor t1 = create_test_tensor(s_shape, d1, false); Tensor expected_res = create_test_tensor(s_shape, exp_d, false); Tensor actual_res = Tensor_max(t1); @@ -27,8 +27,8 @@ void test_max_operator() { { const char* tc_name = "max_vector"; TensorShape v_shape = {5}; - float d1[] = {1.0f, 7.0f, 3.0f, 5.0f, 2.0f}; - float exp_d[] = {7.0f}; // Max is 7 + float d1[] = {8.7458f, 4.147f, 0.9326f, 7.1226f, 2.5115f}; + float exp_d[] = {8.7458f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(v_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); @@ -41,8 +41,8 @@ void test_max_operator() { { const char* tc_name = "max_matrix"; TensorShape m_shape = {2, 3}; - float d1[] = {1.0f, 2.0f, 8.0f, 3.0f, 4.0f, 6.0f}; - float exp_d[] = {8.0f}; // Max is 8 + float d1[] = {7.6507f, -6.481f, 2.9918f, -6.1952f, -9.0693f, 4.4308f}; + float exp_d[] = {7.6507f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(m_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); @@ -55,8 +55,8 @@ void test_max_operator() { { const char* tc_name = "max_vector_negative"; TensorShape v_shape = {4}; - float d1[] = {-1.0f, -2.0f, -3.0f, -0.5f}; - float exp_d[] = {-0.5f}; // Max is -0.5 + float d1[] = {-8.687f, -0.9767f, -9.2835f, -6.0498f}; + float exp_d[] = {-0.9767f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(v_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); @@ -69,8 +69,8 @@ void test_max_operator() { { const char* tc_name = "max_duplicate"; TensorShape v_shape = {5}; - float d1[] = {1.0f, 9.0f, 3.0f, 9.0f, 2.0f}; - float exp_d[] = {9.0f}; // Max is 9, appears twice + float d1[] = {6.1886f, -9.87f, 5.8818f, 5.8818f, 6.1886f}; + float exp_d[] = {6.1886f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(v_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); @@ -83,8 +83,8 @@ void test_max_operator() { { const char* tc_name = "max_3d_tensor"; TensorShape t_shape = {2, 2, 2}; - float d1[] = {1.0f, 2.0f, 3.0f, 12.0f, 5.0f, 6.0f, 7.0f, 8.0f}; - float exp_d[] = {12.0f}; // Max is 12 + float d1[] = {-6.8904f, 9.1443f, -3.2681f, -8.1451f, -8.0657f, 6.9499f, 2.0745f, 6.1426f}; + float exp_d[] = {9.1443f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(t_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); diff --git a/tests/Operator/test_min.c b/tests/Operator/test_min.c index d7eae4c..359ab45 100644 --- a/tests/Operator/test_min.c +++ b/tests/Operator/test_min.c @@ -14,8 +14,8 @@ void test_min_operator() { { const char* tc_name = "min_scalar"; TensorShape s_shape = {1}; - float d1[] = {5.0f}; - float exp_d[] = {5.0f}; + float d1[] = {2.7885f}; + float exp_d[] = {2.7885f}; Tensor t1 = create_test_tensor(s_shape, d1, false); Tensor expected_res = create_test_tensor(s_shape, exp_d, false); Tensor actual_res = Tensor_min(t1); @@ -27,8 +27,8 @@ void test_min_operator() { { const char* tc_name = "min_vector"; TensorShape v_shape = {5}; - float d1[] = {8.0f, 3.0f, 7.0f, 5.0f, 9.0f}; - float exp_d[] = {3.0f}; // Min is 3 + float d1[] = {-9.4998f, -4.4994f, -5.5358f, 4.7294f, 3.534f}; + float exp_d[] = {-9.4998f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(v_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); @@ -41,8 +41,8 @@ void test_min_operator() { { const char* tc_name = "min_matrix"; TensorShape m_shape = {2, 3}; - float d1[] = {5.0f, 2.0f, 8.0f, 3.0f, 4.0f, 6.0f}; - float exp_d[] = {2.0f}; // Min is 2 + float d1[] = {7.8436f, -8.2612f, -1.5616f, -9.4041f, -5.6272f, 0.1071f}; + float exp_d[] = {-9.4041f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(m_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); @@ -55,8 +55,8 @@ void test_min_operator() { { const char* tc_name = "min_vector_negative"; TensorShape v_shape = {4}; - float d1[] = {-1.0f, -2.0f, -3.0f, -0.5f}; - float exp_d[] = {-3.0f}; // Min is -3.0 + float d1[] = {-9.7373f, -8.0315f, -3.5661f, -4.6051f}; + float exp_d[] = {-9.7373f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(v_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); @@ -69,8 +69,8 @@ void test_min_operator() { { const char* tc_name = "min_duplicate"; TensorShape v_shape = {5}; - float d1[] = {5.0f, 2.0f, 8.0f, 2.0f, 7.0f}; - float exp_d[] = {2.0f}; // Min is 2, appears twice + float d1[] = {1.7853f, -9.87f, -2.7956f, -2.7956f, -9.87f}; + float exp_d[] = {-9.87f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(v_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); @@ -83,8 +83,8 @@ void test_min_operator() { { const char* tc_name = "min_3d_tensor"; TensorShape t_shape = {2, 2, 2}; - float d1[] = {10.0f, 20.0f, 5.0f, 12.0f, 15.0f, 16.0f, 7.0f, 18.0f}; - float exp_d[] = {5.0f}; // Min is 5 + float d1[] = {-6.8904f, 9.1443f, -3.2681f, -8.1451f, -8.0657f, 6.9499f, 2.0745f, 6.1426f}; + float exp_d[] = {-8.1451f}; TensorShape exp_shape = {1, 0, 0, 0}; Tensor t1 = create_test_tensor(t_shape, d1, false); Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); From 2d7eaf7209f96ec1cb0a4e7b55e9eba29d7a3107 Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Thu, 3 Jul 2025 20:41:54 +0530 Subject: [PATCH 07/21] backward test cases for min added --- tests/Backward/test_min_backward.c | 97 ++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/Backward/test_min_backward.c diff --git a/tests/Backward/test_min_backward.c b/tests/Backward/test_min_backward.c new file mode 100644 index 0000000..5c3790d --- /dev/null +++ b/tests/Backward/test_min_backward.c @@ -0,0 +1,97 @@ +#include "../../include/cten.h" +#include "../test_utils.h" +#include "../csv_reporter.h" +#include "../test_config.h" +#include + +void test_min_backward() { + const char* op_name = "min_backward"; + PoolId pool_id = 0; + cten_begin_malloc(pool_id); + + // Test Case 1: Vector with a unique minimum value + { + const char* tc_name = "min_vector_unique_backward"; + TensorShape v_shape = {3}; + float data[] = {8.0f, 2.0f, 5.0f}; + float exp_grad[] = {0.0f, 1.0f, 0.0f}; + + Tensor t = create_test_tensor(v_shape, data, true); + Tensor z = Tensor_min(t); + + Tensor grad_dummy = {0}; + Tensor_backward(z, grad_dummy); + + Tensor expected_grad = create_test_tensor(v_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 2: Vector with duplicate minimum values + { + const char* tc_name = "min_vector_duplicate_backward"; + TensorShape v_shape = {4}; + float data[] = {9.0f, 1.0f, 5.0f, 1.0f}; + float exp_grad[] = {0.0f, 0.5f, 0.0f, 0.5f}; + + Tensor t = create_test_tensor(v_shape, data, true); + Tensor z = Tensor_min(t); + + Tensor grad_dummy = {0}; + Tensor_backward(z, grad_dummy); + + Tensor expected_grad = create_test_tensor(v_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 3: Matrix with a unique minimum value + { + const char* tc_name = "min_matrix_unique_backward"; + TensorShape m_shape = {2, 2}; + float data[] = {10.0f, 2.0f, 8.0f, 4.0f}; + float exp_grad[] = {0.0f, 1.0f, 0.0f, 0.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_min(t); + + Tensor grad_dummy = {0}; + Tensor_backward(z, grad_dummy); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 4: Complex computation graph (z = min(x) + y) + { + const char* tc_name = "min_complex_graph_backward"; + TensorShape v_shape = {3}; + TensorShape s_shape = {1}; + float x_data[] = {8.0f, 3.0f, 9.0f}; + float y_data[] = {10.0f}; + + // Let m = min(x). z = m + y. + // dz/dx = dz/dm * dm/dx + // dz/dm = 1.0 (from add op) + // dm/dx = [0, 1, 0] + // dz/dx = 1.0 * [0, 1, 0] = [0, 1.0, 0] + float exp_grad_x[] = {0.0f, 1.0f, 0.0f}; + // dz/dy = 1.0 + float exp_grad_y[] = {1.0f}; + + Tensor x = create_test_tensor(v_shape, x_data, true); + Tensor y = create_test_tensor(s_shape, y_data, true); + + Tensor m = Tensor_min(x); // m = 3.0 + Tensor z = Tensor_add(m, y); // z = 13.0 + + Tensor grad_dummy = {0}; + Tensor_backward(z, grad_dummy); + + Tensor expected_grad_x_tensor = create_test_tensor(v_shape, exp_grad_x, false); + Tensor expected_grad_y_tensor = create_test_tensor(s_shape, exp_grad_y, false); + + compare_tensors(&x.node->grad, &expected_grad_x_tensor, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&y.node->grad, &expected_grad_y_tensor, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + + cten_free(pool_id); +} \ No newline at end of file From 075ac8e150ec07f1c4fac683d98a17cedf8a17ca Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Thu, 3 Jul 2025 20:42:15 +0530 Subject: [PATCH 08/21] backward test cases for sum added --- tests/Backward/test_sum_backward.c | 263 +++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 tests/Backward/test_sum_backward.c diff --git a/tests/Backward/test_sum_backward.c b/tests/Backward/test_sum_backward.c new file mode 100644 index 0000000..ffa218e --- /dev/null +++ b/tests/Backward/test_sum_backward.c @@ -0,0 +1,263 @@ +#include "../../include/cten.h" +#include "../test_utils.h" +#include "../csv_reporter.h" +#include "../test_config.h" +#include + +void test_sum_backward() { + const char* op_name = "sum_backward"; + PoolId pool_id = 0; + cten_begin_malloc(pool_id); + + // Test Case 1: Sum all elements backward + { + const char* tc_name = "Sum_all_backward"; + // Sub-test 1: Vector sum all + { + TensorShape v_shape = {3}; + float data[] = {1.0f, 2.0f, 3.0f}; + float exp_grad[] = {1.0f, 1.0f, 1.0f}; + + Tensor t = create_test_tensor(v_shape, data, true); + Tensor z = Tensor_sum(t); // sum all elements + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(v_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: Matrix sum all + { + TensorShape m_shape = {2, 3}; + float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float exp_grad[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_sum(t); // sum all elements + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 3: 3D tensor sum all + { + TensorShape tensor3d_shape = {2, 2, 2}; + float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}; + float exp_grad[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + + Tensor t = create_test_tensor(tensor3d_shape, data, true); + Tensor z = Tensor_sum(t); // sum all elements + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 3, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 2: Sum along dimension 0 + { + const char* tc_name = "Sum_dim0_backward"; + // Sub-test 1: Matrix sum along dim 0 + { + TensorShape m_shape = {2, 3}; + float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float exp_grad[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_sum(t, 0); // sum along dim 0 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: 3D tensor sum along dim 0 + { + TensorShape tensor3d_shape = {2, 3, 4}; + float data[24]; + for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); + float exp_grad[24]; + for (int i = 0; i < 24; i++) exp_grad[i] = 1.0f; + + Tensor t = create_test_tensor(tensor3d_shape, data, true); + Tensor z = Tensor_sum(t, 0); // sum along dim 0 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 3: Sum along dimension 1 + { + const char* tc_name = "Sum_dim1_backward"; + // Sub-test 1: Matrix sum along dim 1 + { + TensorShape m_shape = {2, 3}; + float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float exp_grad[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_sum(t, 1); // sum along dim 1 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: 3D tensor sum along dim 1 + { + TensorShape tensor3d_shape = {2, 3, 4}; + float data[24]; + for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); + float exp_grad[24]; + for (int i = 0; i < 24; i++) exp_grad[i] = 1.0f; + + Tensor t = create_test_tensor(tensor3d_shape, data, true); + Tensor z = Tensor_sum(t, 1); // sum along dim 1 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 4: Sum along dimension 2 + { + const char* tc_name = "Sum_dim2_backward"; + // Sub-test 1: 3D tensor sum along dim 2 + { + TensorShape tensor3d_shape = {2, 3, 4}; + float data[24]; + for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); + float exp_grad[24]; + for (int i = 0; i < 24; i++) exp_grad[i] = 1.0f; + + Tensor t = create_test_tensor(tensor3d_shape, data, true); + Tensor z = Tensor_sum(t, 2); // sum along dim 2 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 5: Random input sum backward + { + const char* tc_name = "Random_input_sum_backward"; + // Sub-test 1: Random matrix sum along dim 0 + { + TensorShape m_shape = {3, 4}; + float data[] = { + 2.5f, 1.3f, 4.8f, 3.2f, + 0.7f, 5.1f, 2.9f, 6.4f, + 3.6f, 1.8f, 4.2f, 0.9f + }; + float exp_grad[] = { + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_sum(t, 0); // sum along dim 0 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: Random matrix sum along dim 1 + { + TensorShape m_shape = {3, 4}; + float data[] = { + 2.5f, 1.3f, 4.8f, 3.2f, + 0.7f, 5.1f, 2.9f, 6.4f, + 3.6f, 1.8f, 4.2f, 0.9f + }; + float exp_grad[] = { + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + + Tensor t = create_test_tensor(m_shape, data, true); + Tensor z = Tensor_sum(t, 1); // sum along dim 1 + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + } + + // Test Case 6: Chained operations with sum + { + const char* tc_name = "Chained_operations_with_sum"; + // Sub-test 1: Sum(a*b, dim=0) + { + TensorShape m_shape = {2, 3}; + float a_data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float b_data[] = {0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f}; + float exp_grad_a[] = {0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f}; + float exp_grad_b[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + + Tensor a = create_test_tensor(m_shape, a_data, true); + Tensor b = create_test_tensor(m_shape, b_data, true); + Tensor prod = Tensor_mul(a, b); + Tensor z = Tensor_sum(prod, 0); + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad_a = create_test_tensor(m_shape, exp_grad_a, false); + Tensor expected_grad_b = create_test_tensor(m_shape, exp_grad_b, false); + + compare_tensors(&a.node->grad, &expected_grad_a, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&b.node->grad, &expected_grad_b, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Sub-test 2: Sum(a+b, dim=1) + { + TensorShape m_shape = {2, 3}; + float a_data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + float b_data[] = {0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f}; + float exp_grad_a[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + float exp_grad_b[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + + Tensor a = create_test_tensor(m_shape, a_data, true); + Tensor b = create_test_tensor(m_shape, b_data, true); + Tensor sum_ab = Tensor_add(a, b); + Tensor z = Tensor_sum(sum_ab, 1); + + Tensor_backward(z, (Tensor){0}); + + Tensor expected_grad_a = create_test_tensor(m_shape, exp_grad_a, false); + Tensor expected_grad_b = create_test_tensor(m_shape, exp_grad_b, false); + + compare_tensors(&a.node->grad, &expected_grad_a, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + compare_tensors(&b.node->grad, &expected_grad_b, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + } + + cten_free(pool_id); +} \ No newline at end of file From 612fcc709349f9290bfad34a4b2041bc2dc51c53 Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Thu, 3 Jul 2025 21:15:56 +0530 Subject: [PATCH 09/21] cten_test updated --- tests/cten_tests.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/cten_tests.c b/tests/cten_tests.c index c9644e7..33b748e 100644 --- a/tests/cten_tests.c +++ b/tests/cten_tests.c @@ -35,6 +35,8 @@ void test_matmul_backward(); void test_sub_backward(); void test_relu_backward(); void test_linear_backward(); +void test_min_backward(); +void test_max_backward(); int main() { printf("Starting cTensor Test Suite on %s...\n", PLATFORM_NAME); @@ -123,6 +125,12 @@ int main() { test_linear_backward(); printf("Linear backward tests finished.\n"); + test_min_backward(); + printf("Min backward tests finished.\n"); + + test_max_backward(); + printf("Max backward tests finished.\n"); + // other tests csv_reporter_close(); From 608d6cf391b5e3314510fabfc11aa5c85ba378ad Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Fri, 4 Jul 2025 12:36:42 +0530 Subject: [PATCH 10/21] mean and sum test cases removed --- tests/Backward/test_mean_backward.c | 263 ---------------------------- tests/Backward/test_sum_backward.c | 263 ---------------------------- 2 files changed, 526 deletions(-) delete mode 100644 tests/Backward/test_mean_backward.c delete mode 100644 tests/Backward/test_sum_backward.c diff --git a/tests/Backward/test_mean_backward.c b/tests/Backward/test_mean_backward.c deleted file mode 100644 index 1db9d74..0000000 --- a/tests/Backward/test_mean_backward.c +++ /dev/null @@ -1,263 +0,0 @@ -#include "../../include/cten.h" -#include "../test_utils.h" -#include "../csv_reporter.h" -#include "../test_config.h" -#include - -void test_mean_backward() { - const char* op_name = "mean_backward"; - PoolId pool_id = 0; - cten_begin_malloc(pool_id); - - // Test Case 1: Mean all elements backward - { - const char* tc_name = "Mean_all_backward"; - // Sub-test 1: Vector mean all - { - TensorShape v_shape = {3}; - float data[] = {1.0f, 2.0f, 3.0f}; - float exp_grad[] = {1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f}; - - Tensor t = create_test_tensor(v_shape, data, true); - Tensor z = Tensor_mean(t); // mean of all elements - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(v_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: Matrix mean all - { - TensorShape m_shape = {2, 3}; - float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float exp_grad[] = {1.0f/6.0f, 1.0f/6.0f, 1.0f/6.0f, 1.0f/6.0f, 1.0f/6.0f, 1.0f/6.0f}; - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_mean(t); // mean of all elements - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 3: 3D tensor mean all - { - TensorShape tensor3d_shape = {2, 2, 2}; - float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}; - float exp_grad[] = {1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f, 1.0f/8.0f}; - - Tensor t = create_test_tensor(tensor3d_shape, data, true); - Tensor z = Tensor_mean(t); // mean of all elements - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 3, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 2: Mean along dimension 0 - { - const char* tc_name = "Mean_dim0_backward"; - // Sub-test 1: Matrix mean along dim 0 - { - TensorShape m_shape = {2, 3}; - float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float exp_grad[] = {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f}; // 1/2 for each element - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_mean(t, 0); // mean along dim 0 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: 3D tensor mean along dim 0 - { - TensorShape tensor3d_shape = {2, 3, 4}; - float data[24]; - for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); - float exp_grad[24]; - for (int i = 0; i < 24; i++) exp_grad[i] = 0.5f; // 1/2 for each element - - Tensor t = create_test_tensor(tensor3d_shape, data, true); - Tensor z = Tensor_mean(t, 0); // mean along dim 0 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 3: Mean along dimension 1 - { - const char* tc_name = "Mean_dim1_backward"; - // Sub-test 1: Matrix mean along dim 1 - { - TensorShape m_shape = {2, 3}; - float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float exp_grad[] = {1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f}; // 1/3 for each element - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_mean(t, 1); // mean along dim 1 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: 3D tensor mean along dim 1 - { - TensorShape tensor3d_shape = {2, 3, 4}; - float data[24]; - for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); - float exp_grad[24]; - for (int i = 0; i < 24; i++) exp_grad[i] = 1.0f/3.0f; // 1/3 for each element - - Tensor t = create_test_tensor(tensor3d_shape, data, true); - Tensor z = Tensor_mean(t, 1); // mean along dim 1 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 4: Mean along dimension 2 - { - const char* tc_name = "Mean_dim2_backward"; - // Sub-test 1: 3D tensor mean along dim 2 - { - TensorShape tensor3d_shape = {2, 3, 4}; - float data[24]; - for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); - float exp_grad[24]; - for (int i = 0; i < 24; i++) exp_grad[i] = 0.25f; // 1/4 for each element - - Tensor t = create_test_tensor(tensor3d_shape, data, true); - Tensor z = Tensor_mean(t, 2); // mean along dim 2 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 5: Random input mean backward - { - const char* tc_name = "Random_input_mean_backward"; - // Sub-test 1: Random matrix mean along dim 0 - { - TensorShape m_shape = {3, 4}; - float data[] = { - 2.5f, 1.3f, 4.8f, 3.2f, - 0.7f, 5.1f, 2.9f, 6.4f, - 3.6f, 1.8f, 4.2f, 0.9f - }; - float exp_grad[] = { - 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, - 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, - 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f - }; - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_mean(t, 0); // mean along dim 0 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: Random matrix mean along dim 1 - { - TensorShape m_shape = {3, 4}; - float data[] = { - 2.5f, 1.3f, 4.8f, 3.2f, - 0.7f, 5.1f, 2.9f, 6.4f, - 3.6f, 1.8f, 4.2f, 0.9f - }; - float exp_grad[] = { - 0.25f, 0.25f, 0.25f, 0.25f, - 0.25f, 0.25f, 0.25f, 0.25f, - 0.25f, 0.25f, 0.25f, 0.25f - }; - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_mean(t, 1); // mean along dim 1 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 6: Chained operations with mean - { - const char* tc_name = "Chained_operations_with_mean"; - // Sub-test 1: Mean(a*b, dim=0) - { - TensorShape m_shape = {2, 3}; - float a_data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float b_data[] = {0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f}; - float exp_grad_a[] = {0.5f/2.0f, 1.5f/2.0f, 2.5f/2.0f, 3.5f/2.0f, 4.5f/2.0f, 5.5f/2.0f}; - float exp_grad_b[] = {1.0f/2.0f, 2.0f/2.0f, 3.0f/2.0f, 4.0f/2.0f, 5.0f/2.0f, 6.0f/2.0f}; - - Tensor a = create_test_tensor(m_shape, a_data, true); - Tensor b = create_test_tensor(m_shape, b_data, true); - Tensor prod = Tensor_mul(a, b); - Tensor z = Tensor_mean(prod, 0); - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad_a = create_test_tensor(m_shape, exp_grad_a, false); - Tensor expected_grad_b = create_test_tensor(m_shape, exp_grad_b, false); - - compare_tensors(&a.node->grad, &expected_grad_a, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - compare_tensors(&b.node->grad, &expected_grad_b, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: Mean(a+b, dim=1) - { - TensorShape m_shape = {2, 3}; - float a_data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float b_data[] = {0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f}; - float exp_grad_a[] = {1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f}; - float exp_grad_b[] = {1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f}; - - Tensor a = create_test_tensor(m_shape, a_data, true); - Tensor b = create_test_tensor(m_shape, b_data, true); - Tensor sum_ab = Tensor_add(a, b); - Tensor z = Tensor_mean(sum_ab, 1); - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad_a = create_test_tensor(m_shape, exp_grad_a, false); - Tensor expected_grad_b = create_test_tensor(m_shape, exp_grad_b, false); - - compare_tensors(&a.node->grad, &expected_grad_a, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - compare_tensors(&b.node->grad, &expected_grad_b, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - } - - cten_free(pool_id); -} \ No newline at end of file diff --git a/tests/Backward/test_sum_backward.c b/tests/Backward/test_sum_backward.c deleted file mode 100644 index ffa218e..0000000 --- a/tests/Backward/test_sum_backward.c +++ /dev/null @@ -1,263 +0,0 @@ -#include "../../include/cten.h" -#include "../test_utils.h" -#include "../csv_reporter.h" -#include "../test_config.h" -#include - -void test_sum_backward() { - const char* op_name = "sum_backward"; - PoolId pool_id = 0; - cten_begin_malloc(pool_id); - - // Test Case 1: Sum all elements backward - { - const char* tc_name = "Sum_all_backward"; - // Sub-test 1: Vector sum all - { - TensorShape v_shape = {3}; - float data[] = {1.0f, 2.0f, 3.0f}; - float exp_grad[] = {1.0f, 1.0f, 1.0f}; - - Tensor t = create_test_tensor(v_shape, data, true); - Tensor z = Tensor_sum(t); // sum all elements - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(v_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: Matrix sum all - { - TensorShape m_shape = {2, 3}; - float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float exp_grad[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_sum(t); // sum all elements - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 3: 3D tensor sum all - { - TensorShape tensor3d_shape = {2, 2, 2}; - float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}; - float exp_grad[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - - Tensor t = create_test_tensor(tensor3d_shape, data, true); - Tensor z = Tensor_sum(t); // sum all elements - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 3, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 2: Sum along dimension 0 - { - const char* tc_name = "Sum_dim0_backward"; - // Sub-test 1: Matrix sum along dim 0 - { - TensorShape m_shape = {2, 3}; - float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float exp_grad[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_sum(t, 0); // sum along dim 0 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: 3D tensor sum along dim 0 - { - TensorShape tensor3d_shape = {2, 3, 4}; - float data[24]; - for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); - float exp_grad[24]; - for (int i = 0; i < 24; i++) exp_grad[i] = 1.0f; - - Tensor t = create_test_tensor(tensor3d_shape, data, true); - Tensor z = Tensor_sum(t, 0); // sum along dim 0 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 3: Sum along dimension 1 - { - const char* tc_name = "Sum_dim1_backward"; - // Sub-test 1: Matrix sum along dim 1 - { - TensorShape m_shape = {2, 3}; - float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float exp_grad[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_sum(t, 1); // sum along dim 1 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: 3D tensor sum along dim 1 - { - TensorShape tensor3d_shape = {2, 3, 4}; - float data[24]; - for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); - float exp_grad[24]; - for (int i = 0; i < 24; i++) exp_grad[i] = 1.0f; - - Tensor t = create_test_tensor(tensor3d_shape, data, true); - Tensor z = Tensor_sum(t, 1); // sum along dim 1 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 4: Sum along dimension 2 - { - const char* tc_name = "Sum_dim2_backward"; - // Sub-test 1: 3D tensor sum along dim 2 - { - TensorShape tensor3d_shape = {2, 3, 4}; - float data[24]; - for (int i = 0; i < 24; i++) data[i] = (float)(i + 1); - float exp_grad[24]; - for (int i = 0; i < 24; i++) exp_grad[i] = 1.0f; - - Tensor t = create_test_tensor(tensor3d_shape, data, true); - Tensor z = Tensor_sum(t, 2); // sum along dim 2 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(tensor3d_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 5: Random input sum backward - { - const char* tc_name = "Random_input_sum_backward"; - // Sub-test 1: Random matrix sum along dim 0 - { - TensorShape m_shape = {3, 4}; - float data[] = { - 2.5f, 1.3f, 4.8f, 3.2f, - 0.7f, 5.1f, 2.9f, 6.4f, - 3.6f, 1.8f, 4.2f, 0.9f - }; - float exp_grad[] = { - 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f - }; - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_sum(t, 0); // sum along dim 0 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: Random matrix sum along dim 1 - { - TensorShape m_shape = {3, 4}; - float data[] = { - 2.5f, 1.3f, 4.8f, 3.2f, - 0.7f, 5.1f, 2.9f, 6.4f, - 3.6f, 1.8f, 4.2f, 0.9f - }; - float exp_grad[] = { - 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f - }; - - Tensor t = create_test_tensor(m_shape, data, true); - Tensor z = Tensor_sum(t, 1); // sum along dim 1 - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); - - compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - } - - // Test Case 6: Chained operations with sum - { - const char* tc_name = "Chained_operations_with_sum"; - // Sub-test 1: Sum(a*b, dim=0) - { - TensorShape m_shape = {2, 3}; - float a_data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float b_data[] = {0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f}; - float exp_grad_a[] = {0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f}; - float exp_grad_b[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - - Tensor a = create_test_tensor(m_shape, a_data, true); - Tensor b = create_test_tensor(m_shape, b_data, true); - Tensor prod = Tensor_mul(a, b); - Tensor z = Tensor_sum(prod, 0); - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad_a = create_test_tensor(m_shape, exp_grad_a, false); - Tensor expected_grad_b = create_test_tensor(m_shape, exp_grad_b, false); - - compare_tensors(&a.node->grad, &expected_grad_a, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - compare_tensors(&b.node->grad, &expected_grad_b, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); - } - - // Sub-test 2: Sum(a+b, dim=1) - { - TensorShape m_shape = {2, 3}; - float a_data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - float b_data[] = {0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f}; - float exp_grad_a[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - float exp_grad_b[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; - - Tensor a = create_test_tensor(m_shape, a_data, true); - Tensor b = create_test_tensor(m_shape, b_data, true); - Tensor sum_ab = Tensor_add(a, b); - Tensor z = Tensor_sum(sum_ab, 1); - - Tensor_backward(z, (Tensor){0}); - - Tensor expected_grad_a = create_test_tensor(m_shape, exp_grad_a, false); - Tensor expected_grad_b = create_test_tensor(m_shape, exp_grad_b, false); - - compare_tensors(&a.node->grad, &expected_grad_a, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - compare_tensors(&b.node->grad, &expected_grad_b, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); - } - } - - cten_free(pool_id); -} \ No newline at end of file From 64d954ece77c9cd7142f7e32506068cc94004671 Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Sat, 5 Jul 2025 21:26:19 +0530 Subject: [PATCH 11/21] Marco Implementation in Tensor_min and Tensor_max --- include/cten.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/cten.h b/include/cten.h index cb7f63f..fa6ed07 100644 --- a/include/cten.h +++ b/include/cten.h @@ -10,6 +10,8 @@ #define _CTEN_PICK(_1,_2,NAME,...) NAME #define Tensor_mean(...) _CTEN_PICK(__VA_ARGS__, Tensor_mean_dim, Tensor_mean_all)(__VA_ARGS__) #define Tensor_sum(...) _CTEN_PICK(__VA_ARGS__, Tensor_sum_dim, Tensor_sum_all )(__VA_ARGS__) +#define Tensor_max(...) _CTEN_PICK(__VA_ARGS__, Tensor_max_with_indices, Tensor_max_all)(__VA_ARGS__) +#define Tensor_min(...) _CTEN_PICK(__VA_ARGS__, Tensor_min_with_indices, Tensor_min_all)(__VA_ARGS__) typedef int TensorShape[4]; typedef struct GradNode GradNode; @@ -81,9 +83,20 @@ Tensor Tensor_mean_dim(Tensor self, int dim); Tensor Tensor_sum_all (Tensor self); Tensor Tensor_sum_dim (Tensor self, int dim); +Tensor Tensor_max_all(Tensor self); +Tensor Tensor_min_all(Tensor self); +Tensor Tensor_max_dim(Tensor self, int dim); +Tensor Tensor_min_dim(Tensor self, int dim); + Tensor Tensor_max(Tensor self); Tensor Tensor_min(Tensor self); +/* Helper functions for Tensor_max and Tensor_min */ +Tensor Tensor_max_all(Tensor self); +Tensor Tensor_min_all(Tensor self); +Tensor* Tensor_max_with_indices(Tensor self, int dim); +Tensor* Tensor_min_with_indices(Tensor self, int dim); + void Tensor_argmax(Tensor self, int* out); /* Neural Networks */ From 08e1233a6a536e42aa150e78f8e688a356015ce0 Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Sun, 6 Jul 2025 21:24:55 +0530 Subject: [PATCH 12/21] Revert "Marco Implementation in Tensor_min and Tensor_max" This reverts commit 6332e532311129c7aed4884adce017ead81ca0d4. --- include/cten.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/include/cten.h b/include/cten.h index fa6ed07..cb7f63f 100644 --- a/include/cten.h +++ b/include/cten.h @@ -10,8 +10,6 @@ #define _CTEN_PICK(_1,_2,NAME,...) NAME #define Tensor_mean(...) _CTEN_PICK(__VA_ARGS__, Tensor_mean_dim, Tensor_mean_all)(__VA_ARGS__) #define Tensor_sum(...) _CTEN_PICK(__VA_ARGS__, Tensor_sum_dim, Tensor_sum_all )(__VA_ARGS__) -#define Tensor_max(...) _CTEN_PICK(__VA_ARGS__, Tensor_max_with_indices, Tensor_max_all)(__VA_ARGS__) -#define Tensor_min(...) _CTEN_PICK(__VA_ARGS__, Tensor_min_with_indices, Tensor_min_all)(__VA_ARGS__) typedef int TensorShape[4]; typedef struct GradNode GradNode; @@ -83,20 +81,9 @@ Tensor Tensor_mean_dim(Tensor self, int dim); Tensor Tensor_sum_all (Tensor self); Tensor Tensor_sum_dim (Tensor self, int dim); -Tensor Tensor_max_all(Tensor self); -Tensor Tensor_min_all(Tensor self); -Tensor Tensor_max_dim(Tensor self, int dim); -Tensor Tensor_min_dim(Tensor self, int dim); - Tensor Tensor_max(Tensor self); Tensor Tensor_min(Tensor self); -/* Helper functions for Tensor_max and Tensor_min */ -Tensor Tensor_max_all(Tensor self); -Tensor Tensor_min_all(Tensor self); -Tensor* Tensor_max_with_indices(Tensor self, int dim); -Tensor* Tensor_min_with_indices(Tensor self, int dim); - void Tensor_argmax(Tensor self, int* out); /* Neural Networks */ From 0162de663564c2ef9a9c8d8b67ea0d09ed6974fa Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Sun, 6 Jul 2025 22:56:37 +0530 Subject: [PATCH 13/21] Macro for Tensor_min and Tensor_max --- include/cten.h | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/include/cten.h b/include/cten.h index cb7f63f..4ce35bd 100644 --- a/include/cten.h +++ b/include/cten.h @@ -7,6 +7,10 @@ #include #include +#define _CTEN_PICK_REDUCE(_1, _2, NAME, ...) NAME +#define Tensor_max(...) _CTEN_PICK_REDUCE(__VA_ARGS__, Tensor_max_dim, Tensor_max_all)(__VA_ARGS__) +#define Tensor_min(...) _CTEN_PICK_REDUCE(__VA_ARGS__, Tensor_min_dim, Tensor_min_all)(__VA_ARGS__) + #define _CTEN_PICK(_1,_2,NAME,...) NAME #define Tensor_mean(...) _CTEN_PICK(__VA_ARGS__, Tensor_mean_dim, Tensor_mean_all)(__VA_ARGS__) #define Tensor_sum(...) _CTEN_PICK(__VA_ARGS__, Tensor_sum_dim, Tensor_sum_all )(__VA_ARGS__) @@ -33,6 +37,11 @@ typedef struct GradNode { const char* name; } GradNode; +typedef struct { + Tensor values; + Tensor indices; +} TensorMaxMinResult; + void cten_initilize(); void cten_finalize(); @@ -81,8 +90,10 @@ Tensor Tensor_mean_dim(Tensor self, int dim); Tensor Tensor_sum_all (Tensor self); Tensor Tensor_sum_dim (Tensor self, int dim); -Tensor Tensor_max(Tensor self); -Tensor Tensor_min(Tensor self); +Tensor Tensor_max_all(Tensor self); +TensorMaxMinResult Tensor_max_dim(Tensor self, int dim); +Tensor Tensor_min_all(Tensor self); +TensorMaxMinResult Tensor_min_dim(Tensor self, int dim); void Tensor_argmax(Tensor self, int* out); From ef0801a6eadb0a9c6e6d9691c6b01c4b14e44a27 Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Sun, 6 Jul 2025 22:57:51 +0530 Subject: [PATCH 14/21] Max_all and with Dim is added (similary added to Min) --- src/basic.c | 2 +- src/operator.c | 94 +++++++++++++++++++++++++++++++------------------- 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/basic.c b/src/basic.c index 14834e8..a00f939 100644 --- a/src/basic.c +++ b/src/basic.c @@ -154,7 +154,7 @@ void Tensor_backward(Tensor self, Tensor grad) { int input_ndim = TensorShape_dim(input_tensor.shape); int grad_ndim = TensorShape_dim(grad.shape); - if ((strcmp(self.node->name, "Sum") == 0 || strcmp(self.node->name, "Mean") == 0) && input_ndim > grad_ndim) { + if ((strcmp(self.node->name, "Sum") == 0 || strcmp(self.node->name, "Mean") == 0 || strcmp(self.node->name, "MaxDim") == 0 || strcmp(self.node->name, "MinDim") == 0) && input_ndim > grad_ndim) { // Find the dimension that was reduced. We assume the non-reduced dimensions match in size. int unsqueeze_dim = -1; int grad_idx = 0; diff --git a/src/operator.c b/src/operator.c index c3ccaaa..8fdb1d7 100644 --- a/src/operator.c +++ b/src/operator.c @@ -14,6 +14,12 @@ #ifdef Tensor_sum #undef Tensor_sum #endif +#ifdef Tensor_max +#undef Tensor_max +#endif +#ifdef Tensor_min +#undef Tensor_min +#endif static Tensor GradFn_add(Tensor self, int i) { // f(x, y) = x + y; f'(x) = 1; f'(y) = 1 @@ -453,30 +459,59 @@ Tensor Tensor_sub(Tensor self, Tensor other) { return res; } -static Tensor GradFn_max(Tensor self, int i) { - // f(x) = max(x); f'(x) = 1 for elements equal to max, 0 otherwise +Tensor GradFn_reduce_dim(Tensor self, int i) { + Tensor input = self.node->inputs[0]; + Tensor indices_tensor = self.node->inputs[1]; + Tensor grad_out = Tensor_zeros(input.shape, false); + + int out_numel = indices_tensor.data->numel; + int ndim = TensorShape_dim(input.shape); + int reduced_dim = -1; + + for(int d = 0, out_d = 0; d < ndim; d++){ + if(out_d >= TensorShape_dim(self.shape) || input.shape[d] != self.shape[out_d]){ + reduced_dim = d; + break; + } + out_d++; + } + cten_assert(reduced_dim != -1, "Could not determine reduced dimension in gradient calculation"); + + for (int j = 0; j < out_numel; j++) { + int index_along_dim = (int)indices_tensor.data->flex[j]; + + int linear_idx = 0, stride = 1, out_j_rem = j, out_shape_idx = TensorShape_dim(self.shape) - 1; + for (int k = ndim - 1; k >= 0; --k) { + int current_dim_idx; + if (k == reduced_dim) { + current_dim_idx = index_along_dim; + } else { + int dim_k = self.shape[out_shape_idx--]; + current_dim_idx = out_j_rem % dim_k; + out_j_rem /= dim_k; + } + linear_idx += current_dim_idx * stride; + stride *= input.shape[k]; + } + grad_out.data->flex[linear_idx] = 1.0f; + } + return grad_out; +} + +Tensor GradFn_max_all(Tensor self, int i) { Tensor input = self.node->inputs[i]; - Tensor res = Tensor_new(input.shape, false); + Tensor res = Tensor_zeros(input.shape, false); float max_val = self.data->flex[0]; - for (int j = 0; j < res.data->numel; j++) { - res.data->flex[j] = 0.0f; - } - int max_count = 0; for (int j = 0; j < input.data->numel; j++) { - if (input.data->flex[j] == max_val) { - max_count++; - } + if (input.data->flex[j] == max_val) max_count++; } - float grad_value = 1.0f / max_count; + float grad_value = (max_count > 0) ? 1.0f / max_count : 0.0f; for (int j = 0; j < input.data->numel; j++) { - if (input.data->flex[j] == max_val) { - res.data->flex[j] = grad_value; - } + if (input.data->flex[j] == max_val) res.data->flex[j] = grad_value; } - return res; } @@ -487,7 +522,6 @@ Tensor Tensor_max(Tensor self) { bool requires_grad = !cten_is_eval() && (self.node != NULL); Tensor res = Tensor_new((TensorShape){1, 0, 0, 0}, requires_grad); - // Find maximum value float max_val = self.data->flex[0]; for (int i = 1; i < self.data->numel; i++) { if (self.data->flex[i] > max_val) { @@ -498,39 +532,29 @@ Tensor Tensor_max(Tensor self) { res.data->flex[0] = max_val; if (requires_grad) { - res.node->grad_fn = GradFn_max; + res.node->grad_fn = GradFn_max_all; res.node->inputs[0] = self; res.node->n_inputs = 1; - res.node->name = "Max"; + res.node->name = "MaxAll"; } return res; } -static Tensor GradFn_min(Tensor self, int i) { - // f(x) = min(x); f'(x) = 1 for elements equal to min, 0 otherwise +Tensor GradFn_min_all(Tensor self, int i) { Tensor input = self.node->inputs[i]; - Tensor res = Tensor_new(input.shape, false); + Tensor res = Tensor_zeros(input.shape, false); float min_val = self.data->flex[0]; - for (int j = 0; j < res.data->numel; j++) { - res.data->flex[j] = 0.0f; - } - int min_count = 0; for (int j = 0; j < input.data->numel; j++) { - if (input.data->flex[j] == min_val) { - min_count++; - } + if (input.data->flex[j] == min_val) min_count++; } - float grad_value = 1.0f / min_count; + float grad_value = (min_count > 0) ? 1.0f / min_count : 0.0f; for (int j = 0; j < input.data->numel; j++) { - if (input.data->flex[j] == min_val) { - res.data->flex[j] = grad_value; - } + if (input.data->flex[j] == min_val) res.data->flex[j] = grad_value; } - return res; } @@ -552,10 +576,10 @@ Tensor Tensor_min(Tensor self) { res.data->flex[0] = min_val; if (requires_grad) { - res.node->grad_fn = GradFn_min; + res.node->grad_fn = GradFn_min_all; res.node->inputs[0] = self; res.node->n_inputs = 1; - res.node->name = "Min"; + res.node->name = "MinAll"; } return res; From d6a7bd9618b6de2be1b2a03e5fb8d1fce9272ab3 Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Sun, 6 Jul 2025 22:58:56 +0530 Subject: [PATCH 15/21] utility for Tensor_min and max added --- src/utils.c | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 222 insertions(+), 1 deletion(-) diff --git a/src/utils.c b/src/utils.c index 592efc4..8d9da31 100644 --- a/src/utils.c +++ b/src/utils.c @@ -16,6 +16,9 @@ bool va_arg_is_present(va_list args) { Tensor GradFn_mean(Tensor self, int i); Tensor GradFn_sum(Tensor self, int i); +Tensor GradFn_max_all(Tensor self, int i); +Tensor GradFn_min_all(Tensor self, int i); +Tensor GradFn_reduce_dim(Tensor self, int i); Tensor Tensor_mean_all(Tensor self) { float total = 0.0f; @@ -67,6 +70,155 @@ Tensor Tensor_sum_dim(Tensor self, int dim) { return res; } +Tensor Tensor_max_all(Tensor self) { + bool requires_grad = !cten_is_eval() && (self.node != NULL); + Tensor res = Tensor_new((TensorShape){1, 0, 0, 0}, requires_grad); + + if (self.data->numel == 0) cten_assert(false, "max on empty tensor"); + float max_val = self.data->flex[0]; + for (int i = 1; i < self.data->numel; i++) { + if (self.data->flex[i] > max_val) { + max_val = self.data->flex[i]; + } + } + res.data->flex[0] = max_val; + + if (requires_grad) { + res.node->grad_fn = GradFn_max_all; + res.node->inputs[0] = self; + res.node->n_inputs = 1; + res.node->name = "MaxAll"; + } + return res; +} + +TensorMaxMinResult Tensor_max_dim(Tensor self, int dim) { + int ndim = TensorShape_dim(self.shape); + dim = TensorShape_asdim(self.shape, dim); + + TensorShape out_shape = {0}; + int out_shape_len = 0; + for (int i = 0; i < ndim; i++) { + if (i != dim) out_shape[out_shape_len++] = self.shape[i]; + } + + bool requires_grad = !cten_is_eval() && (self.node != NULL); + Tensor values = Tensor_new(out_shape, requires_grad); + Tensor indices = Tensor_new(out_shape, false); + + int dim_size = self.shape[dim]; + for (int i = 0; i < values.data->numel; ++i) { + float best_val = -INFINITY; + int best_idx = -1; + + for (int j = 0; j < dim_size; ++j) { + int in_linear_idx = 0, stride = 1, out_i_rem = i, out_idx_tracker = out_shape_len - 1; + for (int k = ndim - 1; k >= 0; --k) { + int current_dim_idx; + if (k == dim) { + current_dim_idx = j; + } else { + int dim_k = out_shape[out_idx_tracker--]; + current_dim_idx = out_i_rem % dim_k; + out_i_rem /= dim_k; + } + in_linear_idx += current_dim_idx * stride; + stride *= self.shape[k]; + } + float current_val = self.data->flex[in_linear_idx]; + if (current_val > best_val) { best_val = current_val; best_idx = j; } + } + values.data->flex[i] = best_val; + indices.data->flex[i] = (float)best_idx; + } + + if (requires_grad) { + values.node->grad_fn = GradFn_reduce_dim; + values.node->inputs[0] = self; + values.node->inputs[1] = indices; + values.node->n_inputs = 2; + values.node->name = "MaxDim"; + } + + TensorMaxMinResult result = {values, indices}; + return result; +} + +Tensor Tensor_min_all(Tensor self) { + bool requires_grad = !cten_is_eval() && (self.node != NULL); + Tensor res = Tensor_new((TensorShape){1, 0, 0, 0}, requires_grad); + + if (self.data->numel == 0) cten_assert(false, "min on empty tensor"); + float min_val = self.data->flex[0]; + for (int i = 1; i < self.data->numel; i++) { + if (self.data->flex[i] < min_val) { + min_val = self.data->flex[i]; + } + } + res.data->flex[0] = min_val; + + if (requires_grad) { + res.node->grad_fn = GradFn_min_all; + res.node->inputs[0] = self; + res.node->n_inputs = 1; + res.node->name = "MinAll"; + } + return res; +} + +TensorMaxMinResult Tensor_min_dim(Tensor self, int dim) { + int ndim = TensorShape_dim(self.shape); + dim = TensorShape_asdim(self.shape, dim); + + TensorShape out_shape = {0}; + int out_shape_len = 0; + for (int i = 0; i < ndim; i++) { + if (i != dim) out_shape[out_shape_len++] = self.shape[i]; + } + + bool requires_grad = !cten_is_eval() && (self.node != NULL); + Tensor values = Tensor_new(out_shape, requires_grad); + Tensor indices = Tensor_new(out_shape, false); + + int dim_size = self.shape[dim]; + for (int i = 0; i < values.data->numel; ++i) { + float best_val = INFINITY; + int best_idx = -1; + + for (int j = 0; j < dim_size; ++j) { + int in_linear_idx = 0, stride = 1, out_i_rem = i, out_idx_tracker = out_shape_len - 1; + for (int k = ndim - 1; k >= 0; --k) { + int current_dim_idx; + if (k == dim) { + current_dim_idx = j; + } else { + int dim_k = out_shape[out_idx_tracker--]; + current_dim_idx = out_i_rem % dim_k; + out_i_rem /= dim_k; + } + in_linear_idx += current_dim_idx * stride; + stride *= self.shape[k]; + } + float current_val = self.data->flex[in_linear_idx]; + if (current_val < best_val) { best_val = current_val; best_idx = j; } + } + values.data->flex[i] = best_val; + indices.data->flex[i] = (float)best_idx; + } + + if (requires_grad) { + values.node->grad_fn = GradFn_reduce_dim; + values.node->inputs[0] = self; + values.node->inputs[1] = indices; + values.node->n_inputs = 2; + values.node->name = "MinDim"; + } + + TensorMaxMinResult result = {values, indices}; + return result; +} + + void cten_assert(bool cond, const char* fmt, ...) { if(!cond) { va_list args; @@ -91,7 +243,6 @@ void cten_assert_dim(const char* title, int a, int b) { cten_assert(a == b, "%s: %d != %d", title, a, b); } - bool cten_elemwise_broadcast(Tensor* a, Tensor* b) { Tensor orig_a = *a; Tensor orig_b = *b; @@ -365,5 +516,75 @@ Tensor Tensor_unsqueeze(Tensor self, int dim) { Tensor res = self; memcpy(res.shape, new_shape, sizeof(TensorShape)); + return res; +} + +Tensor Tensor_reduce_with_indices(Tensor self, int dim, const char* operation, int* indices_out) { + int ndim = TensorShape_dim(self.shape); + dim = TensorShape_asdim(self.shape, dim); + + TensorShape out_shape = {0}; + int out_idx = 0; + for (int i = 0; i < ndim; i++) { + if (i != dim) { + out_shape[out_idx++] = self.shape[i]; + } + } + + Tensor res = Tensor_new(out_shape, self.node != NULL); + int dim_size = self.shape[dim]; + int total_out_elements = res.data->numel; + + for (int i = 0; i < total_out_elements; ++i) { + float best_val; + int best_idx = -1; + + if (strcmp(operation, "max") == 0) { + best_val = -INFINITY; + } else { // "min" + best_val = INFINITY; + } + + // This loop iterates 'dim_size' times for each output element + for (int j = 0; j < dim_size; ++j) { + // Calculate the linear index in the source tensor + int in_linear_idx = 0; + int stride = 1; + int out_i_rem = i; + + // This logic maps an output index back to an input index + for (int k = ndim - 1; k >= 0; --k) { + int current_dim_idx; + if (k == dim) { + current_dim_idx = j; + } else { + int out_shape_k = out_shape[--out_idx]; + current_dim_idx = out_i_rem % out_shape_k; + out_i_rem /= out_shape_k; + } + in_linear_idx += current_dim_idx * stride; + stride *= self.shape[k]; + } + // Reset out_idx for next iteration of outer loop + out_idx = TensorShape_dim(out_shape); + + float current_val = self.data->flex[in_linear_idx]; + if (strcmp(operation, "max") == 0) { + if (current_val > best_val) { + best_val = current_val; + best_idx = j; + } + } else { // "min" + if (current_val < best_val) { + best_val = current_val; + best_idx = j; + } + } + } + res.data->flex[i] = best_val; + if (indices_out != NULL) { + indices_out[i] = best_idx; + } + } return res; } \ No newline at end of file From a9878e0a3c206246fa9d8089576b7e882bc3a775 Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Mon, 7 Jul 2025 22:10:29 +0530 Subject: [PATCH 16/21] Skip non-differentiable inputs in backward pass --- src/basic.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/basic.c b/src/basic.c index a00f939..15f6bdd 100644 --- a/src/basic.c +++ b/src/basic.c @@ -140,12 +140,11 @@ void Tensor_backward(Tensor self, Tensor grad) { } for(int i = 0; i < self.node->n_inputs; i++) { - if (self.node->inputs[i].data == NULL) { + Tensor input_tensor = self.node->inputs[i]; + if (input_tensor.node == NULL) { continue; } - Tensor input_tensor = self.node->inputs[i]; - // Step 1: Get the local gradient (the partial derivative). --> For z = f(x, y), this would be dz/dx or dz/dy. Tensor input_grad = self.node->grad_fn(self, i); From 17fb365eb41b04c78780f82204dde9113b47ac1e Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Mon, 7 Jul 2025 22:17:55 +0530 Subject: [PATCH 17/21] dim based test cases added --- tests/Operator/test_min.c | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/Operator/test_min.c b/tests/Operator/test_min.c index 359ab45..f93f85d 100644 --- a/tests/Operator/test_min.c +++ b/tests/Operator/test_min.c @@ -92,5 +92,65 @@ void test_min_operator() { compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); } + + // Test Case 7: Min over a specific dimension of a matrix (dim=0) + { + const char* tc_name = "min_matrix_dim_0"; + TensorShape m_shape = {2, 3}; + float d1[] = {5.0f, -1.0f, 7.0f, 2.0f, -8.0f, 6.0f}; + float exp_d[] = {2.0f, -8.0f, 6.0f}; + float exp_idx[] = {1.0f, 1.0f, 1.0f}; + TensorShape exp_shape = {3}; + + Tensor t1 = create_test_tensor(m_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor expected_indices = create_test_tensor(exp_shape, exp_idx, false); + + TensorMaxMinResult actual = Tensor_min(t1, 0); + + compare_tensors(&actual.values, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&actual.indices, &expected_indices, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + + // Test Case 8: Min over a specific dimension of a matrix (dim=1) + { + const char* tc_name = "min_matrix_dim_1"; + TensorShape m_shape = {2, 3}; + float d1[] = {5.0f, -1.0f, 7.0f, 2.0f, -8.0f, 6.0f}; + float exp_d[] = {-1.0f, -8.0f}; + float exp_idx[] = {1.0f, 1.0f}; + TensorShape exp_shape = {2}; + + Tensor t1 = create_test_tensor(m_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor expected_indices = create_test_tensor(exp_shape, exp_idx, false); + + TensorMaxMinResult actual = Tensor_min(t1, 1); + + compare_tensors(&actual.values, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&actual.indices, &expected_indices, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + + // Test Case 9: Min over a dimension of a 3D tensor (dim=2) + { + const char* tc_name = "min_3d_tensor_dim_2"; + TensorShape t_shape = {2, 2, 3}; + float d1[] = {1.0f, 8.0f, -3.0f, 4.0f, 2.0f, 9.0f, + 7.0f, 0.0f, 5.0f, -4.0f, -1.0f, -2.0f}; + + float exp_d[] = {-3.0f, 2.0f, 0.0f, -4.0f}; + float exp_idx[] = {2.0f, 1.0f, 1.0f, 0.0f}; + TensorShape exp_shape = {2, 2}; + + Tensor t1 = create_test_tensor(t_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor expected_indices = create_test_tensor(exp_shape, exp_idx, false); + + TensorMaxMinResult actual = Tensor_min(t1, 2); + + compare_tensors(&actual.values, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&actual.indices, &expected_indices, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + cten_free(pool_id); } From a65c0c6e9cb60615ecc0549f5c97122b3f51dbad Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Mon, 7 Jul 2025 22:21:22 +0530 Subject: [PATCH 18/21] dim basesd test cases add in max operator --- tests/Operator/test_max.c | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/Operator/test_max.c b/tests/Operator/test_max.c index 3cbb60c..dfddec7 100644 --- a/tests/Operator/test_max.c +++ b/tests/Operator/test_max.c @@ -93,5 +93,62 @@ void test_max_operator() { compare_tensors(&actual_res, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); } + // Test Case 7: Max over a specific dimension of a matrix (dim=0) (here dim=-2 is used to represent dim=0) + { + const char* tc_name = "max_matrix_dim_0"; + TensorShape m_shape = {2, 3}; + float d1[] = {5.0f, 9.0f, 7.0f, 2.0f, 1.0f, 8.0f}; + float exp_d[] = {5.0f, 9.0f, 8.0f}; + float exp_idx[] = {0.0f, 0.0f, 1.0f}; + TensorShape exp_shape = {3}; + + Tensor t1 = create_test_tensor(m_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor expected_indices = create_test_tensor(exp_shape, exp_idx, false); + + TensorMaxMinResult actual = Tensor_max(t1, -2); + + compare_tensors(&actual.values, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&actual.indices, &expected_indices, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + + // Test Case 8: Max over a specific dimension of a matrix (dim=1) + { + const char* tc_name = "max_matrix_dim_1"; + TensorShape m_shape = {2, 3}; + float d1[] = {5.0f, 9.0f, 7.0f, 2.0f, 1.0f, 8.0f}; + float exp_d[] = {9.0f, 8.0f}; + float exp_idx[] = {1.0f, 2.0f}; + TensorShape exp_shape = {2}; + + Tensor t1 = create_test_tensor(m_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor expected_indices = create_test_tensor(exp_shape, exp_idx, false); + + TensorMaxMinResult actual = Tensor_max(t1, 1); + + compare_tensors(&actual.values, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&actual.indices, &expected_indices, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + + // Test Case 9: Max over a dimension with duplicate max values (should return first index) + { + const char* tc_name = "max_matrix_dim_1_duplicate"; + TensorShape m_shape = {2, 4}; + float d1[] = {1.0f, 9.0f, 5.0f, 9.0f, 8.0f, 8.0f, 2.0f, 7.0f}; + float exp_d[] = {9.0f, 8.0f}; + float exp_idx[] = {1.0f, 0.0f}; + TensorShape exp_shape = {2}; + + Tensor t1 = create_test_tensor(m_shape, d1, false); + Tensor expected_res = create_test_tensor(exp_shape, exp_d, false); + Tensor expected_indices = create_test_tensor(exp_shape, exp_idx, false); + + TensorMaxMinResult actual = Tensor_max(t1, -1); + + compare_tensors(&actual.values, &expected_res, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + compare_tensors(&actual.indices, &expected_indices, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); + } + cten_free(pool_id); } From 01c4afb06d1650e4361658c8e325ec98e0a4a16b Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Mon, 7 Jul 2025 22:23:51 +0530 Subject: [PATCH 19/21] backward dim based test cases for min operator added --- tests/Backward/test_min_backward.c | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/Backward/test_min_backward.c b/tests/Backward/test_min_backward.c index 5c3790d..a5dad23 100644 --- a/tests/Backward/test_min_backward.c +++ b/tests/Backward/test_min_backward.c @@ -93,5 +93,64 @@ void test_min_backward() { compare_tensors(&y.node->grad, &expected_grad_y_tensor, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); } + // Test Case 5: Gradient of min over a dimension (dim=1) + { + const char* tc_name = "min_matrix_dim1_backward"; + TensorShape m_shape = {2, 3}; + float data[] = {5.0f, 7.0f, -1.0f, -8.0f, 2.0f, 6.0f}; + float exp_grad[] = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + TensorMaxMinResult min_res = Tensor_min(t, 1); + Tensor loss = Tensor_sum(min_res.values); + + Tensor grad_dummy = {0}; + Tensor_backward(loss, grad_dummy); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 6: Gradient of min over a dimension (dim=0) + { + const char* tc_name = "min_matrix_dim0_backward"; + TensorShape m_shape = {3, 2}; + float data[] = {5.0f, 2.0f, -1.0f, 9.0f, 7.0f, -8.0f}; + float exp_grad[] = {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + TensorMaxMinResult min_res = Tensor_min(t, 0); + Tensor loss = Tensor_sum(min_res.values); + + Tensor grad_dummy = {0}; + Tensor_backward(loss, grad_dummy); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 7: Gradient of min over a dimension with duplicate minimums + { + const char* tc_name = "min_matrix_dim_duplicate_backward"; + TensorShape m_shape = {2, 4}; + float data[] = {5.0f, -8.0f, 7.0f, -8.0f, 2.0f, 6.0f, 2.0f, 9.0f}; + + // Min along dim=1 will select the first occurrence of the minimum. + // For row 0, min is -8.0 at index 1. + // For row 1, min is 2.0 at index 0. + // The gradient only flows back to these specific indices. + float exp_grad[] = {0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + TensorMaxMinResult min_res = Tensor_min(t, 1); + Tensor loss = Tensor_sum(min_res.values); + + Tensor grad_dummy = {0}; + Tensor_backward(loss, grad_dummy); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + cten_free(pool_id); } \ No newline at end of file From 0e5dc1c90fef5c06d09884b41ea1b7f48ee5ff41 Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Mon, 7 Jul 2025 22:24:51 +0530 Subject: [PATCH 20/21] backward dim based test cases added for max operator --- tests/Backward/test_max_backward.c | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/Backward/test_max_backward.c b/tests/Backward/test_max_backward.c index 973dc19..ec8c7c0 100644 --- a/tests/Backward/test_max_backward.c +++ b/tests/Backward/test_max_backward.c @@ -93,5 +93,64 @@ void test_max_backward() { compare_tensors(&y.node->grad, &expected_grad_y_tensor, op_name, tc_name, 2, TEST_FLOAT_TOLERANCE); } + // Test Case 5: Gradient of max over a dimension (dim=1) + { + const char* tc_name = "max_matrix_dim1_backward"; + TensorShape m_shape = {2, 3}; + float data[] = {1.0f, 9.0f, 3.0f, 8.0f, 5.0f, 6.0f}; + float exp_grad[] = {0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + TensorMaxMinResult max_res = Tensor_max(t, 1); + Tensor loss = Tensor_sum(max_res.values); + + Tensor grad_dummy = {0}; + Tensor_backward(loss, grad_dummy); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 6: Gradient of max over a dimension (dim=0) + { + const char* tc_name = "max_matrix_dim0_backward"; + TensorShape m_shape = {3, 2}; + float data[] = {5.0f, 2.0f, 1.0f, 9.0f, 7.0f, 8.0f}; + float exp_grad[] = {0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + TensorMaxMinResult max_res = Tensor_max(t, 0); + Tensor loss = Tensor_sum(max_res.values); + + Tensor grad_dummy = {0}; + Tensor_backward(loss, grad_dummy); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + + // Test Case 7: Gradient of max over a dimension with duplicate maximums + { + const char* tc_name = "max_matrix_dim_duplicate_backward"; + TensorShape m_shape = {2, 4}; + float data[] = {5.0f, 9.0f, 7.0f, 9.0f, 8.0f, 6.0f, 8.0f, 1.0f}; + + // Max along dim=1 will select the first occurrence of the maximum. + // For row 0, max is 9.0 at index 1. + // For row 1, max is 8.0 at index 0. + // The gradient only flows back to these specific indices. + float exp_grad[] = {0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}; + + Tensor t = create_test_tensor(m_shape, data, true); + TensorMaxMinResult max_res = Tensor_max(t, 1); + Tensor loss = Tensor_sum(max_res.values); + + Tensor grad_dummy = {0}; + Tensor_backward(loss, grad_dummy); + + Tensor expected_grad = create_test_tensor(m_shape, exp_grad, false); + compare_tensors(&t.node->grad, &expected_grad, op_name, tc_name, 1, TEST_FLOAT_TOLERANCE); + } + cten_free(pool_id); } \ No newline at end of file From a11826ea6b70e20d352009d6d5f4fd4aeee0849a Mon Sep 17 00:00:00 2001 From: Advaitgaur004 Date: Tue, 8 Jul 2025 10:31:02 +0530 Subject: [PATCH 21/21] cleanup - Tensor_reduce_with_indices is initially added in the early development, but later on it is simply redudant. --- src/utils.c | 70 ----------------------------------------------------- 1 file changed, 70 deletions(-) diff --git a/src/utils.c b/src/utils.c index 8d9da31..86babc7 100644 --- a/src/utils.c +++ b/src/utils.c @@ -518,73 +518,3 @@ Tensor Tensor_unsqueeze(Tensor self, int dim) { return res; } - -Tensor Tensor_reduce_with_indices(Tensor self, int dim, const char* operation, int* indices_out) { - int ndim = TensorShape_dim(self.shape); - dim = TensorShape_asdim(self.shape, dim); - - TensorShape out_shape = {0}; - int out_idx = 0; - for (int i = 0; i < ndim; i++) { - if (i != dim) { - out_shape[out_idx++] = self.shape[i]; - } - } - - Tensor res = Tensor_new(out_shape, self.node != NULL); - int dim_size = self.shape[dim]; - int total_out_elements = res.data->numel; - - for (int i = 0; i < total_out_elements; ++i) { - float best_val; - int best_idx = -1; - - if (strcmp(operation, "max") == 0) { - best_val = -INFINITY; - } else { // "min" - best_val = INFINITY; - } - - // This loop iterates 'dim_size' times for each output element - for (int j = 0; j < dim_size; ++j) { - // Calculate the linear index in the source tensor - int in_linear_idx = 0; - int stride = 1; - int out_i_rem = i; - - // This logic maps an output index back to an input index - for (int k = ndim - 1; k >= 0; --k) { - int current_dim_idx; - if (k == dim) { - current_dim_idx = j; - } else { - int out_shape_k = out_shape[--out_idx]; - current_dim_idx = out_i_rem % out_shape_k; - out_i_rem /= out_shape_k; - } - in_linear_idx += current_dim_idx * stride; - stride *= self.shape[k]; - } - // Reset out_idx for next iteration of outer loop - out_idx = TensorShape_dim(out_shape); - - float current_val = self.data->flex[in_linear_idx]; - if (strcmp(operation, "max") == 0) { - if (current_val > best_val) { - best_val = current_val; - best_idx = j; - } - } else { // "min" - if (current_val < best_val) { - best_val = current_val; - best_idx = j; - } - } - } - res.data->flex[i] = best_val; - if (indices_out != NULL) { - indices_out[i] = best_idx; - } - } - return res; -} \ No newline at end of file