1 /*
2 SPDX-FileCopyrightText: 2014-2017 Max Planck Society.
3 All rights reserved.
4
5 SPDX-License-Identifier: BSD-3-Clause
6 */
7
8 /* Created by Edgar Klenske <edgar.klenske@tuebingen.mpg.de>
9 *
10 * Provides the test cases for the Gaussian Process functionality.
11 *
12 */
13
14 #include <gtest/gtest.h>
15 #include <vector>
16 #include <iostream>
17 #include "gaussian_process_guider.h"
18
19 #include <fstream>
20 #include <thread>
21
22 class GPGTest : public ::testing::Test
23 {
24 public:
25 static const double DefaultControlGain; // control gain
26 static const double DefaultPeriodLengthsForInference; // minimal number of period lengths for full prediction
27 static const double DefaultMinMove;
28
29 static const double DefaultLengthScaleSE0Ker; // length-scale of the long-range SE-kernel
30 static const double DefaultSignalVarianceSE0Ker; // signal variance of the long-range SE-kernel
31 static const double DefaultLengthScalePerKer; // length-scale of the periodic kernel
32 static const double DefaultPeriodLengthPerKer; // P_p, period-length of the periodic kernel
33 static const double DefaultSignalVariancePerKer; // signal variance of the periodic kernel
34 static const double DefaultLengthScaleSE1Ker; // length-scale of the short-range SE-kernel
35 static const double DefaultSignalVarianceSE1Ker; // signal variance of the short range SE-kernel
36
37 static const double DefaultPeriodLengthsForPeriodEstimation; // minimal number of period lengts for PL estimation
38 static const int DefaultNumPointsForApproximation; // number of points used in the GP approximation
39 static const double DefaultPredictionGain; // amount of GP prediction to blend in
40
41 static const bool DefaultComputePeriod;
42
43 GaussianProcessGuider* GPG;
44
GPGTest()45 GPGTest(): GPG(0)
46 {
47 GaussianProcessGuider::guide_parameters parameters;
48 parameters.control_gain_ = DefaultControlGain;
49 parameters.min_periods_for_inference_ = DefaultPeriodLengthsForInference;
50 parameters.min_move_ = DefaultMinMove;
51 parameters.SE0KLengthScale_ = DefaultLengthScaleSE0Ker;
52 parameters.SE0KSignalVariance_ = DefaultSignalVarianceSE0Ker;
53 parameters.PKLengthScale_ = DefaultLengthScalePerKer;
54 parameters.PKPeriodLength_ = DefaultPeriodLengthPerKer;
55 parameters.PKSignalVariance_ = DefaultSignalVariancePerKer;
56 parameters.SE1KLengthScale_ = DefaultLengthScaleSE1Ker;
57 parameters.SE1KSignalVariance_ = DefaultSignalVarianceSE1Ker;
58 parameters.min_periods_for_period_estimation_ = DefaultPeriodLengthsForPeriodEstimation;
59 parameters.points_for_approximation_ = DefaultNumPointsForApproximation;
60 parameters.prediction_gain_ = DefaultPredictionGain;
61 parameters.compute_period_ = DefaultComputePeriod;
62
63 GPG = new GaussianProcessGuider(parameters);
64 GPG->SetLearningRate(1.0); // disable smooth learning
65 }
66
~GPGTest()67 ~GPGTest()
68 {
69 delete GPG;
70 }
71 };
72
73 const double GPGTest::DefaultControlGain = 0.8; // control gain
74 const double GPGTest::DefaultPeriodLengthsForInference = 1.0; // minimal number of period lengths for full prediction
75 const double GPGTest::DefaultMinMove = 0.2;
76
77 const double GPGTest::DefaultLengthScaleSE0Ker = 500.0; // length-scale of the long-range SE-kernel
78 const double GPGTest::DefaultSignalVarianceSE0Ker = 10.0; // signal variance of the long-range SE-kernel
79 const double GPGTest::DefaultLengthScalePerKer = 10.0; // length-scale of the periodic kernel
80 const double GPGTest::DefaultPeriodLengthPerKer = 100.0; // P_p, period-length of the periodic kernel
81 const double GPGTest::DefaultSignalVariancePerKer = 10.0; // signal variance of the periodic kernel
82 const double GPGTest::DefaultLengthScaleSE1Ker = 5.0; // length-scale of the short-range SE-kernel
83 const double GPGTest::DefaultSignalVarianceSE1Ker = 1.0; // signal variance of the short range SE-kernel
84
85 const double GPGTest::DefaultPeriodLengthsForPeriodEstimation = 2.0; // minimal number of period lengts for PL estimation
86 const int GPGTest::DefaultNumPointsForApproximation = 100; // number of points used in the GP approximation
87 const double GPGTest::DefaultPredictionGain = 1.0; // amount of GP prediction to blend in
88
89 const bool GPGTest::DefaultComputePeriod = true;
90
91 #include <iterator>
92 #include <iostream>
93 #include <fstream>
94 #include <sstream>
95 #include <vector>
96 #include <string>
97
98 #include "guide_performance_tools.h"
99
TEST_F(GPGTest,simple_result_test)100 TEST_F(GPGTest, simple_result_test)
101 {
102 double result = 0.0;
103
104 // disable hysteresis blending
105 GPG->SetPeriodLengthsInference(0.0);
106
107 // for an empty dataset, deduceResult should return zero
108 result = GPG->deduceResult(3.0);
109 EXPECT_NEAR(result, 0, 1e-6);
110
111 // for an empty dataset, result is equivalent to a P-controller
112 result = GPG->result(1.0, 2.0, 3.0);
113 EXPECT_NEAR(result, 0.8, 1e-6); // result should be measurement x control gain
114
115 GPG->save_gp_data();
116 }
117
TEST_F(GPGTest,period_identification_test)118 TEST_F(GPGTest, period_identification_test)
119 {
120 // first: prepare a nice GP with a sine wave
121 double period_length = 300;
122 double max_time = 10*period_length;
123 int resolution = 500;
124 Eigen::VectorXd timestamps = Eigen::VectorXd::LinSpaced(resolution + 1, 0, max_time);
125 Eigen::VectorXd measurements = 50*(timestamps.array()*2*M_PI/period_length).sin();
126 Eigen::VectorXd controls = 0*measurements;
127 Eigen::VectorXd SNRs = 100*Eigen::VectorXd::Ones(resolution + 1);
128
129 // feed data to the GPGuider
130 for (int i = 0; i < timestamps.size(); ++i)
131 {
132 GPG->inject_data_point(timestamps[i], measurements[i], SNRs[i], controls[i]);
133 }
134 GPG->result(0.15, 2.0, 3.0);
135
136 EXPECT_NEAR(GPG->GetGPHyperparameters()[PKPeriodLength], period_length, 1e0);
137
138 GPG->save_gp_data();
139 }
140
TEST_F(GPGTest,min_move_test)141 TEST_F(GPGTest, min_move_test)
142 {
143 // disable hysteresis blending
144 GPG->SetPeriodLengthsInference(0.0);
145
146 // simple min-moves (without GP data)
147 EXPECT_NEAR(GPG->result(0.15, 2.0, 3.0), 0, 1e-6);
148 GPG->reset();
149 EXPECT_NEAR(GPG->result(0.25, 2.0, 3.0), 0.25*0.8, 1e-6);
150 GPG->reset();
151 EXPECT_NEAR(GPG->result(-0.15, 2.0, 3.0), 0, 1e-6);
152 GPG->reset();
153 EXPECT_NEAR(GPG->result(-0.25, 2.0, 3.0), -0.25*0.8, 1e-6);
154 GPG->reset();
155
156 GPG->save_gp_data();
157 }
158
TEST_F(GPGTest,gp_prediction_test)159 TEST_F(GPGTest, gp_prediction_test)
160 {
161
162 // first: prepare a nice GP with a sine wave
163 double period_length = 300;
164 double max_time = 5*period_length;
165 int resolution = 600;
166 double prediction_length = 3.0;
167 Eigen::VectorXd locations(2);
168 Eigen::VectorXd predictions(2);
169 Eigen::VectorXd timestamps = Eigen::VectorXd::LinSpaced(resolution + 1, 0, max_time);
170 Eigen::VectorXd measurements = 50*(timestamps.array()*2*M_PI/period_length).sin();
171 Eigen::VectorXd controls = 0*measurements;
172 Eigen::VectorXd SNRs = 100*Eigen::VectorXd::Ones(resolution + 1);
173
174 // feed data to the GPGuider
175 for (int i = 0; i < timestamps.size(); ++i)
176 {
177 GPG->inject_data_point(timestamps[i], measurements[i], SNRs[i], controls[i]);
178 }
179 locations << max_time, max_time + prediction_length;
180 predictions = 50*(locations.array()*2*M_PI/period_length).sin();
181 // the first case is with an error smaller than min_move_
182 EXPECT_NEAR(GPG->result(0.15, 2.0, prediction_length, max_time), predictions[1]-predictions[0], 2e-1);
183 GPG->reset();
184
185 // feed data to the GPGuider
186 for (int i = 0; i < timestamps.size(); ++i)
187 {
188 GPG->inject_data_point(timestamps[i], measurements[i], SNRs[i], controls[i]);
189 }
190 // the first case is with an error larger than min_move_
191 EXPECT_NEAR(GPG->result(0.25, 2.0, prediction_length, max_time), 0.25*0.8+predictions[1]-predictions[0], 2e-1);
192
193 GPG->save_gp_data();
194 }
195
TEST_F(GPGTest,parameters_test)196 TEST_F(GPGTest, parameters_test)
197 {
198 EXPECT_NEAR(GPG->GetControlGain(), DefaultControlGain, 1e-6);
199 EXPECT_NEAR(GPG->GetPeriodLengthsInference(), DefaultPeriodLengthsForInference, 1e-6);
200 EXPECT_NEAR(GPG->GetMinMove(), DefaultMinMove, 1e-6);
201
202 std::vector<double> parameters = GPG->GetGPHyperparameters();
203 EXPECT_NEAR(parameters[SE0KLengthScale], DefaultLengthScaleSE0Ker, 1e-6);
204 EXPECT_NEAR(parameters[SE0KSignalVariance], DefaultSignalVarianceSE0Ker, 1e-6);
205 EXPECT_NEAR(parameters[PKLengthScale], DefaultLengthScalePerKer, 1e-6);
206 EXPECT_NEAR(parameters[PKSignalVariance], DefaultSignalVariancePerKer, 1e-6);
207 EXPECT_NEAR(parameters[SE1KLengthScale], DefaultLengthScaleSE1Ker, 1e-6);
208 EXPECT_NEAR(parameters[SE1KSignalVariance], DefaultSignalVarianceSE1Ker, 1e-6);
209 EXPECT_NEAR(parameters[PKPeriodLength], DefaultPeriodLengthPerKer, 1e-6);
210
211 EXPECT_NEAR(GPG->GetPeriodLengthsPeriodEstimation(), DefaultPeriodLengthsForPeriodEstimation, 1e-6);
212 EXPECT_NEAR(GPG->GetNumPointsForApproximation(), DefaultNumPointsForApproximation, 1e-6);
213 EXPECT_NEAR(GPG->GetPredictionGain(), DefaultPredictionGain, 1e-6);
214 EXPECT_NEAR(GPG->GetBoolComputePeriod(), DefaultComputePeriod, 1e-6);
215
216 GPG->save_gp_data();
217 }
218
TEST_F(GPGTest,timer_test)219 TEST_F(GPGTest, timer_test)
220 {
221 int wait = 500;
222
223 GPG->result(1.0, 2.0, 3.0);
224 std::this_thread::sleep_for(std::chrono::milliseconds(wait));
225
226 auto time_start = std::chrono::system_clock::now();
227 GPG->result(1.0, 2.0, 3.0);
228 double first_time = GPG->get_second_last_point().timestamp;
229 std::this_thread::sleep_for(std::chrono::milliseconds(wait));
230 auto time_end = std::chrono::system_clock::now();
231 GPG->result(1.0, 2.0, 3.0);
232 double second_time = GPG->get_second_last_point().timestamp;
233
234 EXPECT_NEAR(second_time - first_time, std::chrono::duration<double>(time_end - time_start).count(), 1e-1);
235
236 GPG->save_gp_data();
237 }
238
TEST_F(GPGTest,gp_projection_test)239 TEST_F(GPGTest, gp_projection_test)
240 { // this test should fail when output projections are disabled and should pass when they are enabled
241
242 // first: prepare a nice GP with a sine wave
243 double period_length = 300;
244 double max_time = 5*period_length;
245 int resolution = 600;
246 double prediction_length = 3.0;
247 Eigen::VectorXd locations(2);
248 Eigen::VectorXd predictions(2);
249 Eigen::VectorXd timestamps = Eigen::VectorXd::LinSpaced(resolution + 1, 0, max_time);
250 Eigen::VectorXd measurements = 50*(timestamps.array()*2*M_PI/period_length).sin();
251 Eigen::VectorXd controls = 0*measurements;
252 Eigen::VectorXd SNRs = 100*Eigen::VectorXd::Ones(resolution + 1);
253
254 Eigen::VectorXd sine_noise = 5*(timestamps.array()*2*M_PI/26).sin(); // smaller "disturbance" to add
255
256 measurements = measurements + sine_noise;
257
258 // feed data to the GPGuider
259 for (int i = 0; i < timestamps.size(); ++i)
260 {
261 GPG->inject_data_point(timestamps[i], measurements[i], SNRs[i], controls[i]);
262 }
263 locations << max_time, max_time + prediction_length;
264 predictions = 50*(locations.array()*2*M_PI/period_length).sin();
265 // the first case is with an error smaller than min_move_
266 EXPECT_NEAR(GPG->result(0.0, 2.0, prediction_length, max_time), predictions[1]-predictions[0], 3e-1);
267 GPG->reset();
268
269 GPG->save_gp_data();
270 }
271
TEST_F(GPGTest,linear_drift_identification_test)272 TEST_F(GPGTest, linear_drift_identification_test)
273 { // when predicting one period length ahead, only linear drift should show
274
275 // first: prepare a nice GP with a sine wave
276 double period_length = 300;
277 double max_time = 3*period_length;
278 int resolution = 300;
279 double prediction_length = period_length; // necessary to only see the drift
280 Eigen::VectorXd locations(2);
281 Eigen::VectorXd predictions(2);
282 Eigen::VectorXd timestamps = Eigen::VectorXd::LinSpaced(resolution + 1, 0, max_time);
283 Eigen::VectorXd measurements = 0*timestamps;
284 Eigen::VectorXd sine_data = 50*(timestamps.array()*2*M_PI/period_length).sin();
285 Eigen::VectorXd drift = 0.25*timestamps; // drift to add
286 Eigen::VectorXd gear_function = sine_data + drift;
287 Eigen::VectorXd controls(timestamps.size());
288 controls << gear_function.tail(gear_function.size()-1) - gear_function.head(gear_function.size()-1), 0;
289 Eigen::VectorXd SNRs = 100*Eigen::VectorXd::Ones(resolution + 1);
290
291 std::vector<double> parameters = GPG->GetGPHyperparameters();
292 parameters[SE0KSignalVariance] = 1e-10; // disable long-range SE kernel
293 parameters[SE1KSignalVariance] = 1e-10; // disable short-range SE kernel
294 parameters[PKPeriodLength] = period_length; // set exact period length
295 GPG->SetBoolComputePeriod(false); // use the exact period length
296 GPG->SetGPHyperparameters(parameters);
297
298 GPG->SetNumPointsForApproximation(2000); // need all data points for exact drift
299
300 // feed data to the GPGuider
301 for (int i = 0; i < timestamps.size(); ++i)
302 {
303 GPG->inject_data_point(timestamps[i], measurements[i], SNRs[i], controls[i]);
304 }
305 locations << 5000, 5000 + prediction_length;
306 predictions = 0.25*locations; // only predict linear drift here
307 // the first case is with an error smaller than min_move_
308 EXPECT_NEAR(GPG->result(0.0, 100.0, prediction_length, max_time), predictions[1]-predictions[0], 2e-1);
309
310 GPG->save_gp_data();
311 }
312
TEST_F(GPGTest,data_preparation_test)313 TEST_F(GPGTest, data_preparation_test)
314 { // no matter whether the gear function shows up in the controls or in the measurements,
315 // the predictions should be identical
316
317 // first: prepare a nice GP with a sine wave
318 double period_length = 300;
319 double max_time = 3*period_length;
320 int resolution = 200;
321 double prediction_length = 3.0;
322 Eigen::VectorXd timestamps = Eigen::VectorXd::LinSpaced(resolution + 1, 0, max_time);
323 Eigen::VectorXd measurements(timestamps.size());
324 Eigen::VectorXd sine_data = 50*(timestamps.array()*2*M_PI/period_length).sin();
325 Eigen::VectorXd controls(timestamps.size());
326 Eigen::VectorXd SNRs = 100*Eigen::VectorXd::Ones(resolution + 1);
327
328 // first option: the error was "compensated" and therefore only shows up in the controls
329 controls << sine_data.tail(sine_data.size()-1) - sine_data.head(sine_data.size()-1), 0;
330 measurements = 0*timestamps;
331
332 // feed data to the GPGuider
333 for (int i = 0; i < timestamps.size(); ++i)
334 {
335 GPG->inject_data_point(timestamps[i], measurements[i], SNRs[i], controls[i]);
336 }
337 double controlled_result = GPG->result(0.0, 2.0, prediction_length, max_time);
338 GPG->reset();
339
340 // second option: the error is not compensated and therefore visible in the measurement
341 controls = 0*controls;
342 measurements = sine_data;
343
344 // feed data to the GPGuider
345 for (int i = 0; i < timestamps.size(); ++i)
346 {
347 GPG->inject_data_point(timestamps[i], measurements[i], SNRs[i], controls[i]);
348 }
349 double measured_result = GPG->result(0.0, 2.0, prediction_length, max_time);
350
351 EXPECT_NEAR(measured_result, controlled_result, 1e-1);
352
353 GPG->save_gp_data();
354 }
355
356 // The period identification should work on real data, with irregular timestamps
TEST_F(GPGTest,real_data_test)357 TEST_F(GPGTest, real_data_test)
358 {
359 double time = 0.0;
360 double measurement = 0.0;
361 double SNR = 0.0;
362 double control = 0.0;
363
364 std::ifstream file("dataset01.csv");
365
366 int i = 0;
367 CSVRow row;
368 while(file >> row)
369 {
370 // ignore special lines: "INFO", "Frame", "DROP"
371 if (row[0][0] == 'I' || row[0][0] == 'F' || row[2][1] == 'D')
372 {
373 continue;
374 }
375 else
376 {
377 ++i;
378 }
379 time = std::stod(row[1]);
380 measurement = std::stod(row[5]);
381 control = std::stod(row[7]);
382 SNR = std::stod(row[16]);
383
384 GPG->inject_data_point(time, measurement, SNR, control);
385 }
386
387 EXPECT_GT(i, 0) << "dataset01.csv was empty or not present";
388
389 GPG->result(0.0, 25.0, 3.0, time);
390
391 EXPECT_NEAR(GPG->GetGPHyperparameters()[PKPeriodLength],483.0,5);
392
393 GPG->save_gp_data();
394 }
395
TEST_F(GPGTest,parameter_filter_test)396 TEST_F(GPGTest, parameter_filter_test)
397 {
398 double period_length = 0.0;
399
400 std::ifstream infile("dataset02.csv");
401
402 Eigen::VectorXd filtered_period_lengths(1000); // must be longer than the dataset
403
404 std::vector<double> hypers = GPG->GetGPHyperparameters();
405 hypers[PKPeriodLength] = 483; // initialize close to final value
406 GPG->SetGPHyperparameters(hypers);
407 GPG->SetLearningRate(0.01);
408
409 int i = 0;
410 CSVRow row;
411 while(infile >> row)
412 {
413 if (row[0][0] == 'p') // ignore the first line
414 {
415 continue;
416 }
417 else
418 {
419 ++i;
420 }
421 period_length = std::stod(row[0]);
422
423 GPG->UpdatePeriodLength(period_length);
424 filtered_period_lengths(i - 1) = GPG->GetGPHyperparameters()[PKPeriodLength];
425 }
426 filtered_period_lengths.conservativeResize(i);
427
428 EXPECT_GT(i, 0) << "dataset02.csv was empty or not present";
429
430 double std_dev = std::numeric_limits<double>::infinity();
431
432 if (i > 10)
433 {
434 Eigen::VectorXd period_lengths_tail = filtered_period_lengths.tail(10);
435 std_dev = math_tools::stdandard_deviation(period_lengths_tail);
436 }
437
438 EXPECT_LT(std_dev, 0.1);
439
440 GPG->save_gp_data();
441 }
442
TEST_F(GPGTest,period_interpolation_test)443 TEST_F(GPGTest, period_interpolation_test)
444 {
445 // first: prepare a nice GP with a sine wave
446 double period_length = 317;
447 double max_time = 2345;
448 int resolution = 527;
449 Eigen::VectorXd timestamps = Eigen::VectorXd::LinSpaced(resolution + 1, 0, max_time);
450 Eigen::VectorXd measurements = 50*(timestamps.array()*2*M_PI/period_length).sin();
451 Eigen::VectorXd controls = 0*measurements;
452 Eigen::VectorXd SNRs = 100*Eigen::VectorXd::Ones(resolution + 1);
453
454 // feed data to the GPGuider
455 for (int i = 0; i < timestamps.size(); ++i)
456 {
457 GPG->inject_data_point(timestamps[i], measurements[i], SNRs[i], controls[i]);
458 }
459 GPG->result(0.15, 2.0, 3.0);
460
461 EXPECT_NEAR(GPG->GetGPHyperparameters()[PKPeriodLength], period_length, 1e0);
462
463 GPG->save_gp_data();
464 }
465
TEST_F(GPGTest,data_regularization_test)466 TEST_F(GPGTest, data_regularization_test)
467 {
468 // first: prepare a nice GP with a sine wave
469 double period_length = 300;
470 double max_time = 20000;
471 int resolution = 8192;
472
473 // second: mess up the grid of time stamps
474 Eigen::VectorXd timestamps(resolution);
475 timestamps << Eigen::VectorXd::LinSpaced(resolution/2, 0, max_time/6),
476 Eigen::VectorXd::LinSpaced(resolution/2, max_time/6 + 0.5, max_time);
477 timestamps += 0.5*math_tools::generate_normal_random_matrix(resolution, 1);
478
479 Eigen::VectorXd measurements = 50*(timestamps.array()*2*M_PI/period_length).sin();
480 Eigen::VectorXd controls = 0*measurements;
481 Eigen::VectorXd SNRs = 100*Eigen::VectorXd::Ones(resolution + 1);
482
483 // feed data to the GPGuider
484 for (int i = 0; i < timestamps.size(); ++i)
485 {
486 GPG->inject_data_point(timestamps[i], measurements[i], SNRs[i], controls[i]);
487 }
488 GPG->result(0.15, 2.0, 3.0);
489
490 EXPECT_NEAR(GPG->GetGPHyperparameters()[PKPeriodLength], period_length, 1);
491
492 GPG->save_gp_data();
493 }
494
495 /**
496 * This "test" is used to log the identified period length to file. This functionality
497 * can be useful for debugging and for assessing the value of the period interpolation,
498 * data regulatization and Kalman filtering techniques.
499 */
TEST_F(GPGTest,DISABLED_log_period_length)500 TEST_F(GPGTest, DISABLED_log_period_length)
501 {
502 double time = 0.0;
503 double measurement = 0.0;
504 double SNR = 0.0;
505 double control = 0.0;
506
507 std::ifstream file("dataset01.csv");
508
509 std::ofstream outfile;
510 outfile.open("period_lengths_reg_int_kf.csv", std::ios_base::out);
511 outfile << "period_length\n";
512
513 int i = 0;
514 CSVRow row;
515 while(file >> row)
516 {
517 // ignore special lines: "INFO", "Frame", "DROP"
518 if (row[0][0] == 'I' || row[0][0] == 'F' || row[2][1] == 'D')
519 {
520 continue;
521 }
522 else
523 {
524 ++i;
525 }
526 time = std::stod(row[1]);
527 measurement = std::stod(row[5]);
528 control = std::stod(row[7]);
529 SNR = std::stod(row[16]);
530
531 GPG->inject_data_point(time, measurement, SNR, control);
532
533 GPG->UpdateGP();
534 outfile << std::setw(8) << GPG->GetGPHyperparameters()[PKPeriodLength] << "\n";
535 }
536 outfile.close();
537 }
538
539 // This is the dataset of an user who experienced a NaN-issue.
540 // It should, of course, return a non-NaN value (a.k.a.: a number).
TEST_F(GPGTest,real_data_test_nan_issue)541 TEST_F(GPGTest, real_data_test_nan_issue)
542 {
543 Eigen::ArrayXXd data = read_data_from_file("dataset03.csv");
544
545 Eigen::ArrayXd controls = data.row(2);
546
547 for (int i = 0; i < data.cols(); ++i)
548 {
549 GPG->inject_data_point(data(0,i), data(1,i), data(3,i), data(2,i));
550 }
551
552 EXPECT_GT(data.cols(), 0) << "dataset was empty or not present";
553
554 double result = GPG->result(0.622, 15.32, 2.0);
555
556 EXPECT_FALSE(math_tools::isNaN(result));
557
558 GPG->save_gp_data();
559 }
560
main(int argc,char ** argv)561 int main(int argc, char** argv)
562 {
563 ::testing::InitGoogleTest(&argc, argv);
564 return RUN_ALL_TESTS();
565 }
566