Skip to content

Commit e85268d

Browse files
committed
test(spline): Implemented data-driven testing for splines
1. Updated `tests/test_data` submodule to the latest commit containing test data for splines. 2. Modified `tests/spline/test_(step|linear|quadratic|cubic).cpp` files to use the new test data. 3. Added `Conditions` test to `QuadraticSplineTest` test suite.
1 parent 0f26a3b commit e85268d

File tree

5 files changed

+207
-2
lines changed

5 files changed

+207
-2
lines changed

tests/spline/test_cubic.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,43 @@
11
#include <gtest/gtest.h>
22

3-
#include <ALFI/dist.h>
43
#include <ALFI/spline/cubic.h>
4+
#include <ALFI/dist.h>
5+
6+
#include "../test_utils.h"
7+
8+
const auto test_data_path = TEST_DATA_DIR "/spline/cubic.toml";
9+
10+
const auto test_data = toml::parse_file(test_data_path);
11+
12+
void test_cubic_spline(double epsilon_coeffs, double epsilon_values) {
13+
const auto& test_cases = test_data["test_cases"].ref<toml::array>();
14+
15+
test_cases.for_each([&](const toml::table& test_case) {
16+
const auto& type = test_case["type"].ref<std::string>();
17+
const auto& X = to_vector<double>(test_case["X"].ref<toml::array>());
18+
const auto& Y = to_vector<double>(test_case["Y"].ref<toml::array>());
19+
const auto& coeffs = to_vector<double>(test_case["coeffs"].ref<toml::array>());
20+
const auto& xx = to_vector<double>(test_case["xx"].ref<toml::array>());
21+
const auto& yy = to_vector<double>(test_case["yy"].ref<toml::array>());
22+
23+
const auto t = [&]() {
24+
if (type == "not-a-knot") {
25+
return alfi::spline::CubicSpline<>::Types::NotAKnot{};
26+
} else {
27+
throw std::runtime_error{"Unexpected type:" + type};
28+
}
29+
}();
30+
31+
const auto spline = alfi::spline::CubicSpline<>(X, Y, t);
32+
expect_eq(spline.coeffs(), coeffs, epsilon_coeffs);
33+
const auto values = spline.eval(xx);
34+
expect_eq(values, yy, epsilon_values);
35+
});
36+
}
37+
38+
TEST(CubicSplineTest, TestData) {
39+
test_cubic_spline(1e-10, 1e-14);
40+
}
541

642
TEST(CubicSplineTest, General) {
743
const std::vector<double> X = {0, 1, 2, 3};

tests/spline/test_linear.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22

33
#include <ALFI/spline/linear.h>
44

5+
#include "../test_utils.h"
6+
7+
const auto test_data_path = TEST_DATA_DIR "/spline/linear.toml";
8+
9+
const auto test_data = toml::parse_file(test_data_path);
10+
11+
void test_linear_spline(double epsilon_coeffs, double epsilon_values) {
12+
const auto& test_cases = test_data["test_cases"].ref<toml::array>();
13+
14+
test_cases.for_each([&](const toml::table& test_case) {
15+
const auto& X = to_vector<double>(test_case["X"].ref<toml::array>());
16+
const auto& Y = to_vector<double>(test_case["Y"].ref<toml::array>());
17+
const auto& coeffs = to_vector<double>(test_case["coeffs"].ref<toml::array>());
18+
const auto& xx = to_vector<double>(test_case["xx"].ref<toml::array>());
19+
const auto& yy = to_vector<double>(test_case["yy"].ref<toml::array>());
20+
21+
const auto spline = alfi::spline::LinearSpline<>(X, Y);
22+
expect_eq(spline.coeffs(), coeffs, epsilon_coeffs);
23+
const auto values = spline.eval(xx);
24+
expect_eq(values, yy, epsilon_values);
25+
});
26+
}
27+
28+
TEST(LinearSplineTest, TestData) {
29+
test_linear_spline(1e-14, 1e-14);
30+
}
31+
532
TEST(LinearSplineTest, General) {
633
const std::vector<double> X = {0, 1, 2, 3};
734
const std::vector<double> Y = {5, 2, 10, 4};

tests/spline/test_quadratic.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,45 @@
11
#include <gtest/gtest.h>
22

33
#include <ALFI/spline/quadratic.h>
4+
#include <ALFI/dist.h>
5+
6+
#include "../test_utils.h"
7+
8+
const auto test_data_path = TEST_DATA_DIR "/spline/quadratic.toml";
9+
10+
const auto test_data = toml::parse_file(test_data_path);
11+
12+
void test_quadratic_spline(double epsilon_coeffs, double epsilon_values) {
13+
const auto& test_cases = test_data["test_cases"].ref<toml::array>();
14+
15+
test_cases.for_each([&](const toml::table& test_case) {
16+
const auto& type = test_case["type"].ref<std::string>();
17+
const auto& X = to_vector<double>(test_case["X"].ref<toml::array>());
18+
const auto& Y = to_vector<double>(test_case["Y"].ref<toml::array>());
19+
const auto& coeffs = to_vector<double>(test_case["coeffs"].ref<toml::array>());
20+
const auto& xx = to_vector<double>(test_case["xx"].ref<toml::array>());
21+
const auto& yy = to_vector<double>(test_case["yy"].ref<toml::array>());
22+
23+
const auto t = [&]() -> alfi::spline::QuadraticSpline<>::Type {
24+
if (type == "semi-not-a-knot") {
25+
return alfi::spline::QuadraticSpline<>::Types::SemiNotAKnot{};
26+
} else if (type == "semi-natural") {
27+
return alfi::spline::QuadraticSpline<>::Types::SemiNatural{};
28+
} else {
29+
throw std::runtime_error{"Unexpected type:" + type};
30+
}
31+
}();
32+
33+
const auto spline = alfi::spline::QuadraticSpline<>(X, Y, t);
34+
expect_eq(spline.coeffs(), coeffs, epsilon_coeffs);
35+
const auto values = spline.eval(xx);
36+
expect_eq(values, yy, epsilon_values);
37+
});
38+
}
39+
40+
TEST(QuadraticSplineTest, TestData) {
41+
test_quadratic_spline(1e-14, 1e-14);
42+
}
443

544
TEST(QuadraticSplineTest, General) {
645
const std::vector<double> X = {0, 1, 2, 3};
@@ -12,6 +51,73 @@ TEST(QuadraticSplineTest, General) {
1251
}
1352
}
1453

54+
TEST(QuadraticSplineTest, Conditions) {
55+
const size_t n = 10;
56+
const double a = -1, b = 1;
57+
const auto X = alfi::dist::uniform(n, a, b);
58+
const auto dX = alfi::util::arrays::diff(X);
59+
const auto Y = alfi::dist::chebyshev_2(n, a, b);
60+
// natural-start, natural-end, semi-natural
61+
auto spline1 = alfi::spline::QuadraticSpline<>(X, Y, alfi::spline::QuadraticSpline<>::Types::NaturalStart{});
62+
EXPECT_NEAR(0, 2*spline1.coeffs()[0], 1e-17);
63+
auto spline2 = alfi::spline::QuadraticSpline<>(X, Y, alfi::spline::QuadraticSpline<>::Types::NaturalEnd{});
64+
EXPECT_NEAR(0, 2*spline2.coeffs()[3*8], 1e-17);
65+
const auto spline3 = alfi::spline::QuadraticSpline<>(X, Y, alfi::spline::QuadraticSpline<>::Types::SemiNatural{});
66+
ASSERT_EQ(spline1.coeffs().size(), spline3.coeffs().size());
67+
ASSERT_EQ(spline2.coeffs().size(), spline3.coeffs().size());
68+
for (size_t i = 0; i < spline3.coeffs().size(); ++i) {
69+
EXPECT_EQ(spline3.coeffs()[i], (spline1.coeffs()[i] + spline2.coeffs()[i]) / 2);
70+
}
71+
// not-a-knot-start, not-a-knot-end, semi-not-a-knot
72+
spline1.construct(X, Y, alfi::spline::QuadraticSpline<>::Types::NotAKnotStart{});
73+
EXPECT_EQ(2*spline1.coeffs()[0], 2*spline1.coeffs()[3*1]);
74+
spline2.construct(X, Y, alfi::spline::QuadraticSpline<>::Types::NotAKnotEnd{});
75+
EXPECT_EQ(2*spline2.coeffs()[3*7], 2*spline2.coeffs()[3*8]);
76+
const auto spline4 = alfi::spline::QuadraticSpline<>(X, Y, alfi::spline::QuadraticSpline<>::Types::SemiNotAKnot{});
77+
ASSERT_EQ(spline1.coeffs().size(), spline4.coeffs().size());
78+
ASSERT_EQ(spline2.coeffs().size(), spline4.coeffs().size());
79+
for (size_t i = 0; i < spline4.coeffs().size(); ++i) {
80+
EXPECT_EQ(spline4.coeffs()[i], (spline1.coeffs()[i] + spline2.coeffs()[i]) / 2);
81+
}
82+
// semi-semi
83+
const auto spline5 = alfi::spline::QuadraticSpline<>(X, Y, alfi::spline::QuadraticSpline<>::Types::SemiSemi{});
84+
ASSERT_EQ(spline3.coeffs().size(), spline5.coeffs().size());
85+
ASSERT_EQ(spline4.coeffs().size(), spline5.coeffs().size());
86+
for (size_t i = 0; i < spline5.coeffs().size(); ++i) {
87+
EXPECT_NEAR(spline5.coeffs()[i], (spline3.coeffs()[i] + spline3.coeffs()[i]) / 2, 1e-15);
88+
}
89+
// clamped
90+
spline1.construct(X, Y, alfi::spline::QuadraticSpline<>::Types::ClampedStart{10});
91+
EXPECT_EQ(10, spline1.coeffs()[1]);
92+
spline2.construct(X, Y, alfi::spline::QuadraticSpline<>::Types::ClampedEnd{10});
93+
EXPECT_NEAR(10, 2*spline2.coeffs()[3*8]*dX[8] + spline2.coeffs()[3*8+1], 1e-14);
94+
const auto spline6 = alfi::spline::QuadraticSpline<>(X, Y, alfi::spline::QuadraticSpline<>::Types::SemiClamped{10, 10});
95+
ASSERT_EQ(spline1.coeffs().size(), spline6.coeffs().size());
96+
ASSERT_EQ(spline2.coeffs().size(), spline6.coeffs().size());
97+
for (size_t i = 0; i < spline6.coeffs().size(); ++i) {
98+
EXPECT_EQ(spline6.coeffs()[i], (spline1.coeffs()[i] + spline2.coeffs()[i]) / 2);
99+
}
100+
spline1.construct(X, Y, alfi::spline::QuadraticSpline<>::Types::Clamped{5, 10});
101+
EXPECT_EQ(10, spline1.coeffs()[3*5+1]);
102+
EXPECT_NEAR(10, 2*spline1.coeffs()[3*4]*dX[8] + spline1.coeffs()[3*4+1], 1e-15);
103+
// fixed-second
104+
spline1.construct(X, Y, alfi::spline::QuadraticSpline<>::Types::FixedSecondStart{10});
105+
EXPECT_EQ(10, 2*spline1.coeffs()[0]);
106+
spline2.construct(X, Y, alfi::spline::QuadraticSpline<>::Types::FixedSecondEnd{10});
107+
EXPECT_EQ(10, 2*spline2.coeffs()[3*8]);
108+
const auto spline7 = alfi::spline::QuadraticSpline<>(X, Y, alfi::spline::QuadraticSpline<>::Types::SemiFixedSecond{10, 10});
109+
ASSERT_EQ(spline1.coeffs().size(), spline7.coeffs().size());
110+
ASSERT_EQ(spline2.coeffs().size(), spline7.coeffs().size());
111+
for (size_t i = 0; i < spline7.coeffs().size(); ++i) {
112+
EXPECT_EQ(spline7.coeffs()[i], (spline1.coeffs()[i] + spline2.coeffs()[i]) / 2);
113+
}
114+
spline1.construct(X, Y, alfi::spline::QuadraticSpline<>::Types::FixedSecond{5, 10});
115+
EXPECT_EQ(10, 2*spline1.coeffs()[3*5]);
116+
// not-a-knot
117+
spline1.construct(X, Y, alfi::spline::QuadraticSpline<>::Types::NotAKnot{2});
118+
EXPECT_EQ(2*spline1.coeffs()[3*1], 2*spline1.coeffs()[3*2]);
119+
}
120+
15121
TEST(QuadraticSplineTest, Moving) {
16122
std::vector<double> X = {0, 1, 2, 3};
17123
const std::vector<double> Y = {5, 2, 10, 4};

tests/spline/test_step.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,42 @@
22

33
#include <ALFI/spline/step.h>
44

5+
#include "../test_utils.h"
6+
7+
const auto test_data_path = TEST_DATA_DIR "/spline/step.toml";
8+
9+
const auto test_data = toml::parse_file(test_data_path);
10+
11+
void test_step_spline(double epsilon) {
12+
const auto& test_cases = test_data["test_cases"].ref<toml::array>();
13+
14+
test_cases.for_each([&](const toml::table& test_case) {
15+
const auto& type = test_case["type"].ref<std::string>();
16+
const auto& X = to_vector<double>(test_case["X"].ref<toml::array>());
17+
const auto& Y = to_vector<double>(test_case["Y"].ref<toml::array>());
18+
const auto& xx = to_vector<double>(test_case["xx"].ref<toml::array>());
19+
const auto& yy = to_vector<double>(test_case["yy"].ref<toml::array>());
20+
21+
const auto t = [&]() -> alfi::spline::StepSpline<>::Type {
22+
if (type == "left") {
23+
return alfi::spline::StepSpline<>::Types::Left{};
24+
} else if (type == "middle") {
25+
return alfi::spline::StepSpline<>::Types::Middle{};
26+
} else if (type == "right") {
27+
return alfi::spline::StepSpline<>::Types::Right{};
28+
} else {
29+
throw std::runtime_error{"Unexpected type:" + type};
30+
}
31+
}();
32+
33+
expect_eq(alfi::spline::StepSpline<>(X, Y, t).eval(xx), yy, epsilon);
34+
});
35+
}
36+
37+
TEST(StepSplineTest, TestData) {
38+
test_step_spline(1e-15);
39+
}
40+
541
TEST(StepSplineTest, General) {
642
const std::vector<double> X = {0, 1, 2, 3};
743
const std::vector<double> Y = {5, 2, 10, 4};

0 commit comments

Comments
 (0)