LCOV - code coverage report
Current view: top level - tests/optim - t_function.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 194 197 98.5 %
Date: 2024-05-13 05:06:06 Functions: 95 96 99.0 %
Legend: Lines: hit not hit

          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             : }

Generated by: LCOV version 1.14