mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
297 lines
11 KiB
C
297 lines
11 KiB
C
/*
|
|
* Copyright (C) 2018 Eistec AB
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "embUnit.h"
|
|
#include "tests-matstat.h"
|
|
|
|
#include "matstat.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
/* White box testing of matstat library */
|
|
|
|
static void test_matstat_basic(void)
|
|
{
|
|
/* nothing special, only verifying the basic functionality */
|
|
matstat_state_t state = MATSTAT_STATE_INIT;
|
|
matstat_add(&state, 10);
|
|
TEST_ASSERT_EQUAL_INT(10, state.min);
|
|
TEST_ASSERT_EQUAL_INT(10, state.max);
|
|
TEST_ASSERT_EQUAL_INT(1, state.count);
|
|
matstat_add(&state, 20);
|
|
TEST_ASSERT_EQUAL_INT(10, state.min);
|
|
TEST_ASSERT_EQUAL_INT(20, state.max);
|
|
TEST_ASSERT_EQUAL_INT(2, state.count);
|
|
matstat_add(&state, 30);
|
|
TEST_ASSERT_EQUAL_INT(10, state.min);
|
|
TEST_ASSERT_EQUAL_INT(30, state.max);
|
|
TEST_ASSERT_EQUAL_INT(3, state.count);
|
|
matstat_add(&state, 40);
|
|
TEST_ASSERT_EQUAL_INT(10, state.min);
|
|
TEST_ASSERT_EQUAL_INT(40, state.max);
|
|
TEST_ASSERT_EQUAL_INT(4, state.count);
|
|
int32_t mean = matstat_mean(&state);
|
|
TEST_ASSERT_EQUAL_INT(25, mean);
|
|
uint64_t var = matstat_variance(&state);
|
|
TEST_ASSERT_EQUAL_INT(166, var);
|
|
matstat_clear(&state);
|
|
TEST_ASSERT_EQUAL_INT(0, state.count);
|
|
}
|
|
|
|
static void test_matstat_var_stability(void)
|
|
{
|
|
/* This test is designed to detect stability errors where the values are
|
|
* located very close together, which should yield a very low variance. */
|
|
/* The initial implementation of the variance algorithm resulted in a very
|
|
* large variance in this test, due to cancellation problems */
|
|
matstat_state_t state = MATSTAT_STATE_INIT;
|
|
matstat_add(&state, 999999);
|
|
matstat_add(&state, 1000000);
|
|
matstat_add(&state, 1000000);
|
|
matstat_add(&state, 1000000);
|
|
matstat_add(&state, 1000000);
|
|
matstat_add(&state, 1000000);
|
|
matstat_add(&state, 1000000);
|
|
matstat_add(&state, 1000000);
|
|
matstat_add(&state, 1000000);
|
|
matstat_add(&state, 1000000);
|
|
int32_t mean = matstat_mean(&state);
|
|
TEST_ASSERT(mean >= 999999);
|
|
TEST_ASSERT(mean <= 1000000);
|
|
uint64_t var = matstat_variance(&state);
|
|
TEST_ASSERT(var <= 1);
|
|
}
|
|
|
|
static void test_matstat_negative_variance(void)
|
|
{
|
|
/* This is a regression test for two related problems where the truncation
|
|
* in the mean computation (integer division) causes the sum_sq value to become
|
|
* negative, or the variance itself to become negative */
|
|
matstat_state_t state = MATSTAT_STATE_INIT;
|
|
matstat_add(&state, -1);
|
|
matstat_add(&state, 0);
|
|
uint64_t var = matstat_variance(&state);
|
|
TEST_ASSERT_EQUAL_INT(0, var);
|
|
matstat_clear(&state);
|
|
matstat_add(&state, 1);
|
|
matstat_add(&state, 0);
|
|
matstat_add(&state, 0);
|
|
matstat_add(&state, 0);
|
|
var = matstat_variance(&state);
|
|
TEST_ASSERT_EQUAL_INT(0, var);
|
|
matstat_clear(&state);
|
|
matstat_add(&state, 1234567);
|
|
for (unsigned int k = 0; k < 9999; ++k) {
|
|
matstat_add(&state, 1234567);
|
|
matstat_add(&state, 1234566);
|
|
}
|
|
var = matstat_variance(&state);
|
|
TEST_ASSERT_EQUAL_INT(0, var);
|
|
}
|
|
|
|
static void test_matstat_merge_basic(void)
|
|
{
|
|
/* This is a basic test of the merging functionality without any "special" cases */
|
|
matstat_state_t state1 = MATSTAT_STATE_INIT;
|
|
matstat_state_t state2 = MATSTAT_STATE_INIT;
|
|
matstat_state_t state_ref = MATSTAT_STATE_INIT;
|
|
matstat_add(&state1, 2000);
|
|
matstat_add(&state_ref, 2000);
|
|
matstat_add(&state1, 1000);
|
|
matstat_add(&state_ref, 1000);
|
|
matstat_add(&state1, 2000);
|
|
matstat_add(&state_ref, 2000);
|
|
matstat_add(&state2, 2000);
|
|
matstat_add(&state_ref, 2000);
|
|
matstat_add(&state2, 2456);
|
|
matstat_add(&state_ref, 2456);
|
|
matstat_add(&state2, 1234);
|
|
matstat_add(&state_ref, 1234);
|
|
matstat_add(&state2, 5678);
|
|
matstat_add(&state_ref, 5678);
|
|
matstat_add(&state2, 9999);
|
|
matstat_add(&state_ref, 9999);
|
|
matstat_merge(&state1, &state2);
|
|
TEST_ASSERT_EQUAL_INT(state_ref.min, state1.min);
|
|
TEST_ASSERT_EQUAL_INT(state_ref.max, state1.max);
|
|
TEST_ASSERT_EQUAL_INT(state_ref.count, state1.count);
|
|
TEST_ASSERT_EQUAL_INT(state_ref.sum, state1.sum);
|
|
int32_t mean = matstat_mean(&state1);
|
|
int32_t mean_ref = matstat_mean(&state_ref);
|
|
TEST_ASSERT_EQUAL_INT(mean_ref, mean);
|
|
}
|
|
|
|
static void test_matstat_merge_empty(void)
|
|
{
|
|
/* Testing merging with one or more empty states */
|
|
matstat_state_t state1 = MATSTAT_STATE_INIT;
|
|
matstat_state_t state2 = MATSTAT_STATE_INIT;
|
|
matstat_merge(&state1, &state2);
|
|
TEST_ASSERT_EQUAL_INT(0, state1.count);
|
|
TEST_ASSERT_EQUAL_INT(0, state2.count);
|
|
TEST_ASSERT_EQUAL_INT(0, state1.sum);
|
|
TEST_ASSERT_EQUAL_INT(0, state2.sum);
|
|
TEST_ASSERT_EQUAL_INT(0, state1.sum_sq);
|
|
TEST_ASSERT_EQUAL_INT(0, state2.sum_sq);
|
|
matstat_add(&state1, 2000);
|
|
matstat_add(&state1, 1000);
|
|
matstat_add(&state1, 2000);
|
|
matstat_merge(&state1, &state2);
|
|
TEST_ASSERT_EQUAL_INT(1000, state1.min);
|
|
TEST_ASSERT_EQUAL_INT(2000, state1.max);
|
|
TEST_ASSERT_EQUAL_INT(3, state1.count);
|
|
TEST_ASSERT_EQUAL_INT(0, state2.count);
|
|
matstat_clear(&state1);
|
|
TEST_ASSERT_EQUAL_INT(0, state1.count);
|
|
matstat_add(&state2, 2000);
|
|
matstat_add(&state2, 1000);
|
|
matstat_add(&state2, 2000);
|
|
TEST_ASSERT_EQUAL_INT(3, state2.count);
|
|
matstat_merge(&state1, &state2);
|
|
TEST_ASSERT_EQUAL_INT(1000, state1.min);
|
|
TEST_ASSERT_EQUAL_INT(2000, state1.max);
|
|
TEST_ASSERT_EQUAL_INT(3, state1.count);
|
|
}
|
|
|
|
static void test_matstat_merge_variance(void)
|
|
{
|
|
/* This test should ensure that merging states from separate sequences will
|
|
* yield correct results for the variance computation */
|
|
matstat_state_t state1 = MATSTAT_STATE_INIT;
|
|
matstat_state_t state2 = MATSTAT_STATE_INIT;
|
|
matstat_state_t state_ref = MATSTAT_STATE_INIT;
|
|
matstat_add(&state1, 2000);
|
|
matstat_add(&state_ref, 2000);
|
|
matstat_add(&state1, 1000);
|
|
matstat_add(&state_ref, 1000);
|
|
matstat_add(&state1, 2000);
|
|
matstat_add(&state_ref, 2000);
|
|
matstat_add(&state2, 9999);
|
|
matstat_add(&state_ref, 9999);
|
|
matstat_add(&state2, 2456);
|
|
matstat_add(&state_ref, 2456);
|
|
matstat_add(&state2, 1234);
|
|
matstat_add(&state_ref, 1234);
|
|
matstat_add(&state2, 5678);
|
|
matstat_add(&state_ref, 5678);
|
|
matstat_add(&state2, 9999);
|
|
matstat_add(&state_ref, 9999);
|
|
matstat_merge(&state1, &state2);
|
|
uint64_t var = matstat_variance(&state1);
|
|
uint64_t var_ref = matstat_variance(&state_ref);
|
|
int64_t var_diff = var - var_ref;
|
|
/* There will invariably be some loss of accuracy because of the integer
|
|
* operations involved in the variance computation. */
|
|
TEST_ASSERT(var_diff < 1000);
|
|
TEST_ASSERT(var_diff > -1000);
|
|
TEST_ASSERT_EQUAL_INT(state_ref.mean, state1.mean);
|
|
}
|
|
|
|
static void test_matstat_merge_variance_regr1(void)
|
|
{
|
|
/* This is a regression check for an issue where the sum_sq variable became
|
|
* negative after merging a sequence of states with different means, and
|
|
* small but non-zero sum_sq values. */
|
|
/* Numbers were taken from a stats dump from the bench_timers application */
|
|
matstat_state_t inputs[] = {
|
|
{ .count = 2686, .sum = 5414, .sum_sq = 1380, .min = 1, .max = 3, .mean = 2 },
|
|
{ .count = 2643, .sum = 5272, .sum_sq = 3263, .min = 1, .max = 3, .mean = 1 },
|
|
{ .count = 2650, .sum = 5328, .sum_sq = 719, .min = 1, .max = 3, .mean = 2 },
|
|
{ .count = 2562, .sum = 5117, .sum_sq = 2756, .min = 1, .max = 3, .mean = 1 },
|
|
{ .count = 2579, .sum = 5157, .sum_sq = 635, .min = 1, .max = 3, .mean = 1 },
|
|
{ .count = 2533, .sum = 5050, .sum_sq = 2944, .min = 1, .max = 3, .mean = 1 },
|
|
{ .count = 2630, .sum = 5276, .sum_sq = 1078, .min = 1, .max = 3, .mean = 2 },
|
|
{ .count = 2667, .sum = 5333, .sum_sq = 974, .min = 1, .max = 3, .mean = 1 },
|
|
{ .count = 2414, .sum = 4859, .sum_sq = 1074, .min = 1, .max = 3, .mean = 2 },
|
|
};
|
|
matstat_state_t merged = MATSTAT_STATE_INIT;
|
|
for (unsigned k = 0; k < ARRAY_SIZE(inputs); ++k) {
|
|
matstat_merge(&merged, &inputs[k]);
|
|
}
|
|
int64_t var = (int64_t)matstat_variance(&merged);
|
|
/* Expected variance for this input is 0, because of integer truncation of the result.
|
|
* The bug gave the following result instead:
|
|
* count = 23364, sum = 46806, sum_sq = 18446744073709540510, mean = 2, var = 789570863061659
|
|
*/
|
|
/* Left here for debugging test case failures: */
|
|
/* printf("\nmerged: count = %" PRIu32 ", sum = %" PRId64 ", sum_sq = %" PRIu64 ", "
|
|
"mean = %" PRId32 ", var = %" PRIu64 "\n", merged.count, merged.sum,
|
|
merged.sum_sq, merged.mean, var); */
|
|
TEST_ASSERT((int64_t)merged.sum_sq > 0);
|
|
TEST_ASSERT(var >= 0);
|
|
}
|
|
|
|
static void test_matstat_accuracy(void)
|
|
{
|
|
/* This test verifies that the numeric accuracy is "good enough" */
|
|
matstat_state_t state = MATSTAT_STATE_INIT;
|
|
/*
|
|
* The test values below were sampled from a normal distribution with
|
|
* mean = 12345
|
|
* standard deviation = 10000 => variance = 100000000
|
|
*
|
|
* The sample distribution, when computed with double precision floating
|
|
* point values, is:
|
|
* sample variance = 115969073.207895
|
|
* sample mean = 12293.05
|
|
*/
|
|
/* This test will fail unless the library adaptively adjusts the offset to
|
|
* reduce the error in the variance */
|
|
matstat_add(&state, -9228);
|
|
matstat_add(&state, 6225);
|
|
matstat_add(&state, 15935);
|
|
matstat_add(&state, 1171);
|
|
matstat_add(&state, 9500);
|
|
matstat_add(&state, 22805);
|
|
matstat_add(&state, 6484);
|
|
matstat_add(&state, 10157);
|
|
matstat_add(&state, 23870);
|
|
matstat_add(&state, 9010);
|
|
matstat_add(&state, 16093);
|
|
matstat_add(&state, 20969);
|
|
matstat_add(&state, 18077);
|
|
matstat_add(&state, 9202);
|
|
matstat_add(&state, 20074);
|
|
matstat_add(&state, 19236);
|
|
matstat_add(&state, 32276);
|
|
matstat_add(&state, 6342);
|
|
matstat_add(&state, 18759);
|
|
matstat_add(&state, -11096);
|
|
int32_t mean = matstat_mean(&state);
|
|
uint64_t var = matstat_variance(&state);
|
|
int64_t var_diff = var - 115969073;
|
|
TEST_ASSERT(var_diff < 10000);
|
|
TEST_ASSERT(var_diff > -10000);
|
|
TEST_ASSERT_EQUAL_INT(12293, mean);
|
|
}
|
|
|
|
Test *tests_matstat_tests(void)
|
|
{
|
|
EMB_UNIT_TESTFIXTURES(fixtures) {
|
|
new_TestFixture(test_matstat_basic),
|
|
new_TestFixture(test_matstat_var_stability),
|
|
new_TestFixture(test_matstat_merge_basic),
|
|
new_TestFixture(test_matstat_merge_empty),
|
|
new_TestFixture(test_matstat_merge_variance),
|
|
new_TestFixture(test_matstat_merge_variance_regr1),
|
|
new_TestFixture(test_matstat_accuracy),
|
|
new_TestFixture(test_matstat_negative_variance),
|
|
};
|
|
|
|
EMB_UNIT_TESTCALLER(matstat_tests, NULL, NULL, fixtures);
|
|
|
|
return (Test *)&matstat_tests;
|
|
}
|
|
|
|
void tests_matstat(void)
|
|
{
|
|
TESTS_RUN(tests_matstat_tests());
|
|
}
|