Something went wrong on our end
-
Peter Thedens authoredPeter Thedens authored
test_computation_graph.c 12.06 KiB
#include "unity.h"
#include "computation_graph.h"
#include "graph_blocks.h"
#define GRAPH_TEST_EPS 0.00001
void test_adding_2_numbers() {
struct computation_graph *graph = create_graph();
int block = graph_add_defined_block(graph, BLOCK_ADD, "Add");
int cblock3 = graph_add_defined_block(graph, BLOCK_CONSTANT, "3");
graph_set_param_val(graph, cblock3, CONST_SET, 3);
int cblock4 = graph_add_defined_block(graph, BLOCK_CONSTANT, "4");
graph_set_param_val(graph, cblock4, CONST_SET, 4);
graph_set_source(graph, block, ADD_SUMMAND1, cblock3, CONST_VAL);
graph_set_source(graph, block, ADD_SUMMAND2, cblock4, CONST_VAL);
int to_compute_for[1] = {block};
graph_compute_nodes(graph, to_compute_for, 1);
double result = graph_get_output(graph, block, ADD_SUM);
TEST_ASSERT_DOUBLE_WITHIN(GRAPH_TEST_EPS, 7, result);
}
void test_computing_cycles() {
struct computation_graph *graph = create_graph();
int gain1 = graph_add_defined_block(graph, BLOCK_GAIN, "gain1");
int gain2 = graph_add_defined_block(graph, BLOCK_GAIN, "gain2");
graph_set_source(graph, gain2, GAIN_INPUT, gain1, GAIN_RESULT);
graph_set_source(graph, gain1, GAIN_INPUT, gain2, GAIN_RESULT);
int to_compute_for[1] = {gain2};
graph_compute_nodes(graph, to_compute_for, 1);
// If no infinite loop, then success. Value is undefined for circular graphs
}
void test_resetting_cycles() {
struct computation_graph *graph = create_graph();
int acum1 = graph_add_defined_block(graph, BLOCK_ACCUMULATE, "accumulator1");
int acum2 = graph_add_defined_block(graph, BLOCK_ACCUMULATE, "accumulator2");
graph_set_source(graph, acum2, ACCUM_IN, acum1, ACCUMULATED);
graph_set_source(graph, acum1, ACCUM_IN, acum2, ACCUMULATED);
// Passes if no infinite loop
}
// Tests the accumulator block, thereby testing reset and state changes
void test_accumulator_state() {
struct computation_graph *graph = create_graph();
int cblock = graph_add_defined_block(graph, BLOCK_CONSTANT, "const");
int acum_b = graph_add_defined_block(graph, BLOCK_ACCUMULATE, "accumulator");
graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL);
int to_compute_for[1] = {acum_b};
graph_set_param_val(graph, cblock, CONST_SET, 3);
graph_compute_nodes(graph, to_compute_for, 1);
graph_set_param_val(graph, cblock, CONST_SET, 8);
graph_compute_nodes(graph, to_compute_for, 1);
graph_set_param_val(graph, cblock, CONST_SET, -2);
graph_compute_nodes(graph, to_compute_for, 1);
double result = graph_get_output(graph, acum_b, ACCUMULATED);
TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(GRAPH_TEST_EPS, 9, result, "graph_test_accumulator failed on step 1");
// Test reset on source set
int gain_b = graph_add_defined_block(graph, BLOCK_GAIN, "Gain");
graph_set_param_val(graph, gain_b, GAIN_GAIN, 1);
graph_set_source(graph, gain_b, GAIN_INPUT, acum_b, ACCUMULATED);
to_compute_for[0] = gain_b;
graph_compute_nodes(graph, to_compute_for, 1);
result = graph_get_output(graph, gain_b, GAIN_RESULT);
TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(GRAPH_TEST_EPS, -2, result, "graph_test_accumulator failed on step 2");
}
// Tests that a block will only execute once per compute,
// even if its output is connected to multiple inputs
void test_that_blocks_only_get_executed_once() {
struct computation_graph *graph = create_graph();
int acum_b = graph_add_defined_block(graph, BLOCK_ACCUMULATE, "accumulator");
int add_block = graph_add_defined_block(graph, BLOCK_ADD, "Add");
int cblock = graph_add_defined_block(graph, BLOCK_CONSTANT, "const");
graph_set_param_val(graph, cblock, CONST_SET, 2);
graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL);
graph_set_source(graph, add_block, ADD_SUMMAND1, acum_b, ACCUMULATED);
graph_set_source(graph, add_block, ADD_SUMMAND2, acum_b, ACCUMULATED);
int to_compute_for[1] = {add_block};
graph_compute_nodes(graph, to_compute_for, 1);
double result = graph_get_output(graph, add_block, ADD_SUM);
TEST_ASSERT_DOUBLE_WITHIN(GRAPH_TEST_EPS, 4, result);
}
// Tests that upon connection of a second child, a block will not reset
void tests_that_already_connected_blocks_dont_get_reset() {
struct computation_graph *graph = create_graph();
int cblock = graph_add_defined_block(graph, BLOCK_CONSTANT, "5");
graph_set_param_val(graph, cblock, CONST_SET, 5);
int acum_b = graph_add_defined_block(graph, BLOCK_ACCUMULATE, "accumulator");
int gain1 = graph_add_defined_block(graph, BLOCK_GAIN, "gain1");
graph_set_param_val(graph, gain1, GAIN_GAIN, 1);
graph_set_source(graph, gain1, GAIN_INPUT, acum_b, ACCUMULATED);
graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL);
int to_compute_for[1] = {gain1};
graph_compute_nodes(graph, to_compute_for, 1);
// state of acum_b is now 5
int gain2 = graph_add_defined_block(graph, BLOCK_GAIN, "gain2");
graph_set_param_val(graph, gain2, GAIN_GAIN, 1);
// Connect gain 2, and accumulator should not get reset
graph_set_source(graph, gain2, GAIN_INPUT, acum_b, ACCUMULATED);
to_compute_for[0] = gain2;
graph_compute_nodes(graph, to_compute_for, 1);
double result = graph_get_output(graph, gain2, GAIN_RESULT);
// Equals 5 and not 10 because the inputs to the accumulator did not change,
// so it didn't run again'
TEST_ASSERT_DOUBLE_WITHIN(GRAPH_TEST_EPS, 5, result);
}
void test_that_a_self_loop_computation_terminates() {
struct computation_graph *graph = create_graph();
int gain1 = graph_add_defined_block(graph, BLOCK_GAIN, "gain1");
graph_set_source(graph, gain1, GAIN_INPUT, gain1, GAIN_RESULT);
int to_compute_for[1] = {gain1};
graph_compute_nodes(graph, to_compute_for, 1);
}
void test_that_nodes_only_update_when_their_inputs_change() {
struct computation_graph *graph = create_graph();
int cblock = graph_add_defined_block(graph, BLOCK_CONSTANT, "const");
int acum_b = graph_add_defined_block(graph, BLOCK_ACCUMULATE, "accumulator");
graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL);
graph_set_param_val(graph, cblock, CONST_SET, 3);
int to_compute_for[1] = {acum_b};
graph_compute_nodes(graph, to_compute_for, 1);
graph_compute_nodes(graph, to_compute_for, 1);
double result = graph_get_output(graph, acum_b, ACCUMULATED);
TEST_ASSERT_DOUBLE_WITHIN(GRAPH_TEST_EPS, 3, result);
}
/*
C1 --->| accum_b1 --->|
| Add --->
C2 --->| accum_b2 --->|
*/
void test_that_updates_propagate_only_to_their_children() {
struct computation_graph *graph = create_graph();
int cblock1 = graph_add_defined_block(graph, BLOCK_CONSTANT, "const1");
int cblock2 = graph_add_defined_block(graph, BLOCK_CONSTANT, "const2");
int accum_b1 = graph_add_defined_block(graph, BLOCK_ACCUMULATE, "accumulator1");
int accum_b2 = graph_add_defined_block(graph, BLOCK_ACCUMULATE, "accumulator2");
int add_b = graph_add_defined_block(graph, BLOCK_ADD, "add");
graph_set_source(graph, accum_b1, ACCUM_IN, cblock1, CONST_VAL);
graph_set_source(graph, accum_b2, ACCUM_IN, cblock2, CONST_VAL);
graph_set_source(graph, add_b, ADD_SUMMAND1, accum_b1, ACCUMULATED);
graph_set_source(graph, add_b, ADD_SUMMAND2, accum_b2, ACCUMULATED);
graph_set_param_val(graph, cblock1, CONST_SET, 5);
graph_set_param_val(graph, cblock2, CONST_SET, 2);
int to_compute_for[] = {add_b};
graph_compute_nodes(graph, to_compute_for, 1);
double result1 = graph_get_output(graph, add_b, ADD_SUM);
graph_set_param_val(graph, cblock1, CONST_SET, 1);
graph_compute_nodes(graph, to_compute_for, 1);
double result2 = graph_get_output(graph, add_b, ADD_SUM);
TEST_ASSERT_DOUBLE_WITHIN(GRAPH_TEST_EPS, 7, result1);
TEST_ASSERT_DOUBLE_WITHIN(GRAPH_TEST_EPS, 8, result2);
}
/*
Tests for a really subtle edge case
If a node is disconnected from the computation path, then it will not get computed, even if it is "updated"
After computation, nodes get their updated flag cleared. It used to clear all nodes, even if they weren't processed (now it only clears if they were processed)
This caused problems, because if a node had its output to two things, then it wouldn't get marked updated by the reset propagation.
Since it didn't get marked as "updated" when it got connected to the computation path, and it had its original "updated" flag cleared,
the node would never get set.
*/
void test_that_nodes_get_executed_when_updated_even_if_disconnected() {
printf("\n\n---------\n");
struct computation_graph *graph = create_graph();
int d_block = graph_add_defined_block(graph, BLOCK_CONSTANT, "const1");
int gain_block = graph_add_defined_block(graph, BLOCK_GAIN, "gain");
int gain2_block = graph_add_defined_block(graph, BLOCK_GAIN, "gain2");
int const_b = graph_add_defined_block(graph, BLOCK_CONSTANT, "const2");
graph_set_source(graph, gain_block, GAIN_INPUT, const_b, CONST_VAL);
graph_set_source(graph, gain2_block, GAIN_INPUT, d_block, CONST_VAL); // We need this so d_block doesn't get updated
graph_set_param_val(graph, gain_block, GAIN_GAIN, 2);
graph_set_param_val(graph, d_block, CONST_SET, 1.2345); // Make sure this gets set even if disconnected
int to_compute_for[] = {gain_block};
graph_compute_nodes(graph, to_compute_for, 1);
graph_set_source(graph, gain_block, GAIN_INPUT, d_block, CONST_VAL);
graph_compute_nodes(graph, to_compute_for, 1);
double set_val = graph_get_output(graph, gain_block, GAIN_RESULT);
TEST_ASSERT_DOUBLE_WITHIN(GRAPH_TEST_EPS, 2*1.2345, set_val);
}
void test_that_the_get_source_call_works_normally() {
struct computation_graph *graph = create_graph();
int add_block = graph_add_defined_block(graph, BLOCK_ADD, "Add");
int cblock3 = graph_add_defined_block(graph, BLOCK_CONSTANT, "3");
graph_set_source(graph, add_block, ADD_SUMMAND1, cblock3, CONST_VAL);
struct node_src source = graph_get_source(graph, add_block, ADD_SUMMAND1);
TEST_ASSERT_EQUAL_INT(cblock3, source.controller_id);
TEST_ASSERT_EQUAL_INT(CONST_VAL, source.controller_output);
}
void test_that_the_get_source_call_returns_ID_neg_1_when_invalid_ID_is_passed() {
struct computation_graph *graph = create_graph();
int add_block = graph_add_defined_block(graph, BLOCK_ADD, "Add");
struct node_src source = graph_get_source(graph, 123, ADD_SUMMAND1);
TEST_ASSERT_EQUAL_INT(-1, source.controller_id);
}
void test_that_new_nodes_can_be_created_by_ID() {
struct computation_graph *graph = create_graph();
int desired_id = 87;
int add_block = graph_add_node_id(graph, desired_id, "Add", &node_add_type);
TEST_ASSERT_EQUAL_INT(desired_id, add_block);
int const1 = graph_add_node_id(graph, 12, "const1", &node_const_type);
graph_set_param_val(graph, const1, CONST_SET, 3.5);
int const2 = graph_add_node_id(graph, 123, "const2", &node_const_type);
graph_set_param_val(graph, const2, CONST_SET, 2.5);
graph_set_source(graph, add_block, ADD_SUMMAND1, const1, CONST_VAL);
graph_set_source(graph, add_block, ADD_SUMMAND2, const2, CONST_VAL);
int to_compute_for[] = {add_block};
graph_compute_nodes(graph, to_compute_for, 1);
double result = graph_get_output(graph, add_block, ADD_SUM);
printf("n_nodes: %d, size: %d\n", graph->n_nodes, graph->size);
printf("result: %f", result);
TEST_ASSERT_DOUBLE_WITHIN(GRAPH_TEST_EPS, 3.5 + 2.5, result);
}
int main() {
UNITY_BEGIN();
// RUN_TEST(test_adding_2_numbers);
RUN_TEST(test_computing_cycles);
// RUN_TEST(test_resetting_cycles);
// RUN_TEST(test_accumulator_state);
// RUN_TEST(test_that_blocks_only_get_executed_once);
// RUN_TEST(tests_that_already_connected_blocks_dont_get_reset);
// RUN_TEST(test_that_a_self_loop_computation_terminates);
// RUN_TEST(test_that_nodes_only_update_when_their_inputs_change);
// RUN_TEST(test_that_updates_propagate_only_to_their_children);
// RUN_TEST(test_that_nodes_get_executed_when_updated_even_if_disconnected);
// RUN_TEST(test_that_the_get_source_call_works_normally);
// RUN_TEST(test_that_the_get_source_call_returns_ID_neg_1_when_invalid_ID_is_passed);
// RUN_TEST(test_that_new_nodes_can_be_created_by_ID);
return UNITY_END();
}