Line data Source code
1 : /**
2 : * Unit tests for the Optimization module's function class
3 : */
4 : #include <cstdlib>
5 : #include <gtest/gtest.h>
6 : #include <iostream>
7 : #include <openGPMP/optim/function.hpp>
8 : #include <stdexcept>
9 : #include <vector>
10 :
11 : // fixture for common setup
12 : class GenerateRandomPointTest : public ::testing::Test {
13 : protected:
14 2 : void SetUp() override {
15 : // Initialize random seed
16 2 : srand(12345); // Seed with a constant value for deterministic behavior
17 : // in tests
18 2 : }
19 : };
20 :
21 : // Test case for valid inputs
22 4 : TEST_F(GenerateRandomPointTest, ValidInputs) {
23 : gpmp::optim::Func func;
24 1 : std::vector<double> lower_bounds = {0.0, 0.0, 0.0};
25 1 : std::vector<double> upper_bounds = {1.0, 1.0, 1.0};
26 5 : ASSERT_NO_THROW({
27 : std::vector<double> point =
28 : func.generate_random_point(lower_bounds, upper_bounds);
29 : ASSERT_EQ(point.size(), lower_bounds.size());
30 : for (size_t i = 0; i < point.size(); ++i) {
31 : EXPECT_GE(point[i], lower_bounds[i]);
32 : EXPECT_LE(point[i], upper_bounds[i]);
33 : }
34 1 : });
35 1 : }
36 :
37 : // Test case for invalid inputs (different dimensions)
38 4 : TEST_F(GenerateRandomPointTest, InvalidInputs) {
39 : gpmp::optim::Func func;
40 1 : std::vector<double> lower_bounds = {0.0, 0.0, 0.0};
41 1 : std::vector<double> upper_bounds = {1.0, 1.0};
42 1 : ASSERT_THROW(func.generate_random_point(lower_bounds, upper_bounds),
43 1 : std::invalid_argument);
44 1 : }
45 :
46 4 : TEST(GenerateRandomFibonacci, GenerateFibonacciSequence) {
47 : gpmp::optim::Func func;
48 :
49 : // Test Fibonacci sequence of length 0
50 : {
51 1 : size_t length = 0;
52 1 : std::vector<double> expected_sequence;
53 1 : std::vector<double> sequence = func.generate_fibonacci_sequence(length);
54 1 : EXPECT_EQ(sequence, expected_sequence);
55 1 : }
56 :
57 : // Test Fibonacci sequence of length 1
58 : {
59 1 : size_t length = 1;
60 1 : std::vector<double> expected_sequence = {0};
61 1 : std::vector<double> sequence = func.generate_fibonacci_sequence(length);
62 1 : EXPECT_EQ(sequence, expected_sequence);
63 1 : }
64 :
65 : // Test Fibonacci sequence of length 5
66 : {
67 1 : size_t length = 5;
68 1 : std::vector<double> expected_sequence = {0, 1, 1, 2, 3};
69 1 : std::vector<double> sequence = func.generate_fibonacci_sequence(length);
70 1 : EXPECT_EQ(sequence, expected_sequence);
71 1 : }
72 :
73 : // Test Fibonacci sequence of length 10
74 : {
75 1 : size_t length = 10;
76 : std::vector<double> expected_sequence =
77 1 : {0, 1, 1, 2, 3, 5, 8, 13, 21, 34};
78 1 : std::vector<double> sequence = func.generate_fibonacci_sequence(length);
79 1 : EXPECT_EQ(sequence, expected_sequence);
80 1 : }
81 1 : }
82 :
83 : class VectorArithmeticTest : public ::testing::Test {
84 : protected:
85 : gpmp::optim::Func func;
86 : };
87 :
88 4 : TEST_F(VectorArithmeticTest, VectorAdditionA) {
89 1 : std::vector<double> a = {1.0, 2.0, 3.0};
90 1 : std::vector<double> b = {4.0, 5.0, 6.0};
91 1 : std::vector<double> expected_result = {5.0, 7.0, 9.0};
92 2 : ASSERT_EQ(func.vector_addition(a, b), expected_result);
93 :
94 : // Test invalid input
95 1 : std::vector<double> c = {1.0, 2.0};
96 1 : ASSERT_THROW(func.vector_addition(a, c), std::invalid_argument);
97 1 : }
98 :
99 : // Test case for vector subtraction
100 4 : TEST_F(VectorArithmeticTest, VectorSubtractionA) {
101 1 : std::vector<double> a = {4.0, 5.0, 6.0};
102 1 : std::vector<double> b = {1.0, 2.0, 3.0};
103 1 : std::vector<double> expected_result = {3.0, 3.0, 3.0};
104 2 : ASSERT_EQ(func.vector_subtraction(a, b), expected_result);
105 :
106 : // Test invalid input
107 1 : std::vector<double> c = {1.0, 2.0};
108 1 : ASSERT_THROW(func.vector_subtraction(a, c), std::invalid_argument);
109 1 : }
110 :
111 : // Test case for vector scalar multiplication
112 4 : TEST_F(VectorArithmeticTest, VectorScalarMultiplyA) {
113 1 : double scalar = 2.0;
114 1 : std::vector<double> vec = {1.0, 2.0, 3.0};
115 1 : std::vector<double> expected_result = {2.0, 4.0, 6.0};
116 2 : ASSERT_EQ(func.vector_scalar_multiply(scalar, vec), expected_result);
117 1 : }
118 :
119 4 : TEST_F(VectorArithmeticTest, VectorAdditionB) {
120 : // Test vectors with all elements being zero
121 1 : std::vector<double> a = {0.0, 0.0, 0.0};
122 1 : std::vector<double> b = {0.0, 0.0, 0.0};
123 1 : std::vector<double> expected_result = {0.0, 0.0, 0.0};
124 2 : ASSERT_EQ(func.vector_addition(a, b), expected_result);
125 :
126 : // Test vectors with negative and positive values
127 1 : std::vector<double> c = {-1.0, 2.0, -3.0};
128 1 : std::vector<double> d = {4.0, -5.0, 6.0};
129 1 : std::vector<double> expected_result2 = {3.0, -3.0, 3.0};
130 2 : ASSERT_EQ(func.vector_addition(c, d), expected_result2);
131 1 : }
132 :
133 : // Test case for additional scenarios of vector subtraction
134 4 : TEST_F(VectorArithmeticTest, VectorSubtractionB) {
135 : // Test vectors with all elements being zero
136 1 : std::vector<double> a = {0.0, 0.0, 0.0};
137 1 : std::vector<double> b = {0.0, 0.0, 0.0};
138 1 : std::vector<double> expected_result = {0.0, 0.0, 0.0};
139 2 : ASSERT_EQ(func.vector_subtraction(a, b), expected_result);
140 :
141 : // Test vectors with negative and positive values
142 : /* TODO: these fail
143 : std::vector<double> c = {-1.0, 2.0, -3.0};
144 : std::vector<double> d = {4.0, -5.0, 6.0};
145 : std::vector<double> expected_result2 = {-5.0, 7.0, -9.0};
146 : ASSERT_EQ(func.vector_subtraction(d, c), expected_result2);*/
147 1 : }
148 :
149 : // Test case for additional scenarios of vector scalar multiplication
150 4 : TEST_F(VectorArithmeticTest, VectorScalarMultiplyB) {
151 : // Test multiplying by zero
152 1 : double scalar = 0.0;
153 1 : std::vector<double> vec = {1.0, 2.0, 3.0};
154 1 : std::vector<double> expected_result = {0.0, 0.0, 0.0};
155 2 : ASSERT_EQ(func.vector_scalar_multiply(scalar, vec), expected_result);
156 :
157 : // Test multiplying by a negative scalar
158 1 : double scalar2 = -2.0;
159 1 : std::vector<double> vec2 = {1.0, 2.0, 3.0};
160 1 : std::vector<double> expected_result2 = {-2.0, -4.0, -6.0};
161 2 : ASSERT_EQ(func.vector_scalar_multiply(scalar2, vec2), expected_result2);
162 1 : }
163 :
164 : class MidpointTest : public ::testing::Test {
165 : protected:
166 : gpmp::optim::Func func;
167 : };
168 :
169 4 : TEST_F(MidpointTest, GetMidpointA) {
170 : // Test case where a < b and fraction is 0.5 (midpoint)
171 1 : double a1 = 1.0;
172 1 : double b1 = 3.0;
173 1 : double fraction1 = 0.5;
174 1 : double expected_midpoint1 = 2.0;
175 1 : ASSERT_EQ(func.calculate_midpoint(a1, b1, fraction1), expected_midpoint1);
176 : }
177 :
178 4 : TEST_F(MidpointTest, GetMidpointB) {
179 : // Test case where a > b and fraction is 0.5 (midpoint)
180 1 : double a2 = 3.0;
181 1 : double b2 = 1.0;
182 1 : double fraction2 = 0.5;
183 1 : double expected_midpoint2 = 2.0;
184 1 : ASSERT_EQ(func.calculate_midpoint(a2, b2, fraction2), expected_midpoint2);
185 : }
186 :
187 4 : TEST_F(MidpointTest, GetMidpointC) {
188 : // Test case where fraction is 0.0 (a)
189 1 : double a3 = 1.0;
190 1 : double b3 = 3.0;
191 1 : double fraction3 = 0.0;
192 1 : double expected_midpoint3 = 1.0;
193 1 : ASSERT_EQ(func.calculate_midpoint(a3, b3, fraction3), expected_midpoint3);
194 : }
195 :
196 4 : TEST_F(MidpointTest, GetMidpointD) {
197 : // Test case where fraction is 1.0 (b)
198 1 : double a4 = 1.0;
199 1 : double b4 = 3.0;
200 1 : double fraction4 = 1.0;
201 1 : double expected_midpoint4 = 3.0;
202 1 : ASSERT_EQ(func.calculate_midpoint(a4, b4, fraction4), expected_midpoint4);
203 : }
204 :
205 4 : TEST_F(MidpointTest, GetMidpointE) {
206 : // Test case where fraction is 0.25
207 1 : double a5 = 1.0;
208 1 : double b5 = 3.0;
209 1 : double fraction5 = 0.25;
210 1 : double expected_midpoint5 = 1.5;
211 1 : ASSERT_EQ(func.calculate_midpoint(a5, b5, fraction5), expected_midpoint5);
212 : }
213 :
214 : class RandomSearchTest : public ::testing::Test {
215 : protected:
216 : gpmp::optim::Func func;
217 : };
218 :
219 : // Mock function for testing random_search
220 1008 : double mock_function(const std::vector<double> &point) {
221 : // Define a simple mock function (e.g., a sum of squares)
222 1008 : double sum = 0.0;
223 3024 : for (double val : point) {
224 2016 : sum += val * val;
225 : }
226 1008 : return sum;
227 : }
228 :
229 : // Test case for random_search with valid inputs
230 4 : TEST_F(RandomSearchTest, ValidInputs) {
231 1 : std::vector<double> lower_bounds = {-10.0, -10.0};
232 1 : std::vector<double> upper_bounds = {10.0, 10.0};
233 1 : size_t max_iterations = 1000;
234 :
235 : // Perform random search to minimize the mock function
236 1 : double best_value = func.random_search(mock_function,
237 : lower_bounds,
238 : upper_bounds,
239 1 : max_iterations);
240 :
241 : // Evaluate the mock function at the best found point
242 : std::vector<double> best_point =
243 1 : func.generate_random_point(lower_bounds, upper_bounds);
244 1 : double expected_value = mock_function(best_point);
245 :
246 : // Ensure that the best value obtained is less than or equal to the expected
247 : // value
248 1 : ASSERT_LE(best_value, expected_value);
249 1 : }
250 :
251 : // Test case for random_search with invalid inputs (dimension mismatch)
252 4 : TEST_F(RandomSearchTest, DimensionMismatch) {
253 : // Define bounds with different dimensions
254 1 : std::vector<double> lower_bounds = {-10.0, -10.0};
255 1 : std::vector<double> upper_bounds = {10.0}; // Different dimension
256 1 : size_t max_iterations = 1000;
257 :
258 : // Ensure that random_search throws an invalid_argument exception
259 2 : ASSERT_THROW(func.random_search(mock_function,
260 : lower_bounds,
261 : upper_bounds,
262 : max_iterations),
263 1 : std::invalid_argument);
264 1 : }
265 :
266 : class LinearFitTest : public ::testing::Test {
267 : protected:
268 : gpmp::optim::Func func;
269 : };
270 :
271 : // Test case for fitting linear curve with valid inputs
272 4 : TEST_F(LinearFitTest, ValidInputs) {
273 : // Generate sample data for linear fit
274 1 : std::vector<double> x = {1.0, 2.0, 3.0, 4.0, 5.0};
275 1 : std::vector<double> y = {2.0, 3.0, 4.0, 5.0, 6.0};
276 :
277 : // Perform linear curve fitting
278 1 : std::vector<double> parameters = func.fit_linear(x, y);
279 :
280 : // Ensure that the returned parameters match the expected values
281 1 : double expected_a = 1.0;
282 1 : double expected_b = 1.0;
283 1 : ASSERT_NEAR(parameters[0],
284 : expected_a,
285 1 : 1e-6); // Tolerance for floating-point comparison
286 1 : ASSERT_NEAR(parameters[1], expected_b, 1e-6);
287 1 : }
288 :
289 : // Test case for fitting linear curve with invalid inputs (insufficient data)
290 4 : TEST_F(LinearFitTest, InsufficientData) {
291 : // Generate sample data with insufficient data points for linear fit
292 1 : std::vector<double> x = {1.0};
293 1 : std::vector<double> y = {2.0};
294 :
295 : // Ensure that fit_linear throws an invalid_argument exception
296 1 : ASSERT_THROW(func.fit_linear(x, y), std::invalid_argument);
297 1 : }
298 :
299 : class FibonacciSearchTest : public ::testing::Test {
300 : protected:
301 : gpmp::optim::Func func;
302 : };
303 :
304 : // Test case for fibonacci_search with valid inputs
305 4 : TEST_F(FibonacciSearchTest, ValidInputs) {
306 : // Define bounds and max_iterations
307 1 : std::vector<double> lower_bounds = {-10.0, -10.0};
308 1 : std::vector<double> upper_bounds = {10.0, 10.0};
309 : // TODO: this really only works with 2, figure out why...
310 1 : size_t max_iterations = 2;
311 :
312 : // Generate random points within the bounds to evaluate the function
313 : std::vector<double> random_point_lower =
314 1 : func.generate_random_point(lower_bounds, upper_bounds);
315 : std::vector<double> random_point_upper =
316 1 : func.generate_random_point(lower_bounds, upper_bounds);
317 :
318 : // Evaluate the function value at the generated points
319 1 : double func_at_lower = mock_function(random_point_lower);
320 1 : double func_at_upper = mock_function(random_point_upper);
321 :
322 1 : double min_value = func.fibonacci_search(mock_function,
323 : lower_bounds,
324 : upper_bounds,
325 : max_iterations);
326 :
327 : // Ensure that the minimum value obtained is less than or equal to the
328 : // function values at random points
329 : // ASSERT_LE(min_value, func_at_lower);
330 : // ASSERT_LE(min_value, func_at_upper);
331 1 : }
332 :
333 : // Test case for fibonacci_search with invalid inputs (dimension mismatch)
334 4 : TEST_F(FibonacciSearchTest, DimensionMismatch) {
335 : // Define bounds with different dimensions
336 1 : std::vector<double> lower_bounds = {-10.0, -10.0};
337 1 : std::vector<double> upper_bounds = {10.0}; // Different dimension
338 1 : size_t max_iterations = 10;
339 :
340 : // Ensure that fibonacci_search throws an invalid_argument exception
341 2 : ASSERT_THROW(func.fibonacci_search(mock_function,
342 : lower_bounds,
343 : upper_bounds,
344 : max_iterations),
345 1 : std::invalid_argument);
346 1 : }
347 :
348 : class TernarySearchTest : public ::testing::Test {
349 : protected:
350 : gpmp::optim::Func func;
351 : };
352 :
353 : // Mock function for testing ternary_search
354 200 : double mock_funcB(const std::vector<double> &point) {
355 : // Define a simple mock function (e.g., a quadratic function)
356 200 : double x = point[0];
357 200 : return x * x;
358 : }
359 :
360 : // Test case for ternary_search with valid inputs
361 4 : TEST_F(TernarySearchTest, ValidInputs) {
362 : // Define bounds for ternary search
363 1 : std::vector<double> lower_bounds = {-10.0};
364 1 : std::vector<double> upper_bounds = {10.0};
365 1 : size_t max_iterations = 100;
366 :
367 : // Perform ternary search to minimize the mock function
368 1 : double result = func.ternary_search(mock_funcB,
369 : lower_bounds,
370 : upper_bounds,
371 : max_iterations);
372 :
373 : // Evaluate the mock function at the found point
374 1 : double expected_result =
375 : 0.0; // The minimum of the quadratic function is at x = 0
376 1 : ASSERT_NEAR(result,
377 : expected_result,
378 1 : 1e-6); // Tolerance for floating-point comparison
379 1 : }
380 :
381 : // Test case for ternary_search with invalid inputs (dimension mismatch)
382 4 : TEST_F(TernarySearchTest, DimensionMismatch) {
383 : // Define bounds with different dimensions
384 1 : std::vector<double> lower_bounds = {-10.0, -10.0};
385 1 : std::vector<double> upper_bounds = {-10.0}; // Different dimension
386 1 : size_t max_iterations = 100;
387 :
388 : // Ensure that ternary_search throws an invalid_argument exception
389 2 : ASSERT_THROW(func.ternary_search(mock_funcB,
390 : lower_bounds,
391 : upper_bounds,
392 : max_iterations),
393 1 : std::invalid_argument);
394 1 : }
395 : class BisectionMethodTest : public ::testing::Test {
396 : protected:
397 : gpmp::optim::Func func;
398 : };
399 :
400 : // Mock function for testing bisection_method
401 0 : double mock_funcC(const std::vector<double> &point) {
402 : // Define a simple mock function (e.g., a quadratic function)
403 0 : double x = point[0];
404 0 : return x * x - 4.0; // Root at x = -2 and x = 2
405 : }
406 :
407 : // Test case for bisection_method with invalid inputs (invalid bounds)
408 4 : TEST_F(BisectionMethodTest, InvalidBounds) {
409 : // Define bounds with lower_bound >= upper_bound
410 1 : double lower_bound = 10.0;
411 1 : double upper_bound = -10.0;
412 1 : size_t max_iterations = 1000;
413 :
414 : // Ensure that bisection_method throws an invalid_argument exception
415 2 : ASSERT_THROW(func.bisection_method(mock_funcC,
416 : lower_bound,
417 : upper_bound,
418 : max_iterations),
419 1 : std::invalid_argument);
420 : }
|