• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

libKriging / libKriging / 20211252547

14 Dec 2025 05:03PM UTC coverage: 67.712% (+30.3%) from 37.371%
20211252547

push

github

web-flow
Merge pull request #303 from libKriging/multistart-optim_jemalloc

Multistart optim

3946 of 4387 new or added lines in 30 files covered. (89.95%)

20 existing lines in 5 files now uncovered.

8403 of 12410 relevant lines covered (67.71%)

53457.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.75
/tests/KrigingTest.cpp
1
// Must be before any include that might include <cmath>
2
#define _USE_MATH_DEFINES
3

4
#define CATCH_CONFIG_MAIN
5
#include "libKriging/utils/lk_armadillo.hpp"
6
#define CATCH_CONFIG_ENABLE_BENCHMARKING
7
#include <catch2/catch.hpp>
8
#include <chrono>
9
#include <cmath>
10
#include <fstream>
11
#include <sstream>
12
#include <vector>
13
#include <libKriging/Kriging.hpp>
14
#include <libKriging/KrigingLoader.hpp>
15
#include <libKriging/Trend.hpp>
16

17
auto f = [](const arma::rowvec& row) {
1,146✔
18
  double sum = 0;
1,146✔
19
  for (auto&& x : row) {
5,270✔
20
    // sum += ((x - .5) * (x - .5));  // cas 1
21
    sum += (x * x);  // cas 2
4,124✔
22
  }
23
  return sum;
1,146✔
24
};
25

26
auto prepare_and_run_bench = [](auto&& bench) {
22✔
27
  const int count = 11;
22✔
28
  const auto i = GENERATE_COPY(range(0, count));
24✔
29

30
  arma::arma_rng::seed_type seed_val = 123;  // populate somehow (fixed value => reproducible)
22✔
31

32
  const double logn = 1 + 0.1 * i;
22✔
33
  const arma::uword n = floor(std::pow(10., logn));
22✔
34
  const arma::uword d = floor(2 + i / 3);
22✔
35

36
  INFO("dimensions are n=" << n << " x d=" << d);
22✔
37

38
  arma::arma_rng::set_seed(seed_val);
22✔
39
  arma::mat X(n, d, arma::fill::randu);
22✔
40
  arma::colvec y(n);
22✔
41
  for (arma::uword k = 0; k < n; ++k)
908✔
42
    y(k) = f(X.row(k));
1,772✔
43

44
  bench(y, X, i);
22✔
45

46
  CHECK(true);
22✔
47
};
22✔
48

49
TEST_CASE("workflow") {
11✔
50
  prepare_and_run_bench([](const arma::colvec& y, const arma::mat& X, int) {
11✔
51
    Kriging ok = Kriging("gauss");
22✔
52
    Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
11✔
53
    ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS", "LL", parameters);  // FIXME no move
11✔
54
    const double theta = 0.5;
11✔
55
    arma::vec theta_vec(X.n_cols);
11✔
56
    theta_vec.fill(theta);
11✔
57
    return std::get<1>(ok.logLikelihoodFun(theta_vec, true, false, false));
33✔
58
  });
11✔
59
}
11✔
60

61
TEST_CASE("save & reload") {
11✔
62
  prepare_and_run_bench([](const arma::colvec& y, const arma::mat& X, int) {
11✔
63
    Kriging ok = Kriging("gauss");
22✔
64
    Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
11✔
65
    ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS", "LL", parameters);  // FIXME no move
11✔
66
    ok.save("dump.json");
11✔
67

68
    Kriging ok_reloaded = Kriging::load("dump.json");
22✔
69
    assert(KrigingLoader::describe("dump.json") == KrigingLoader::KrigingType::Kriging);
11✔
70

71
    const double theta = 0.5;
11✔
72
    arma::vec theta_vec(X.n_cols);
11✔
73
    theta_vec.fill(theta);
11✔
74
    return std::get<1>(ok_reloaded.logLikelihoodFun(theta_vec, true, false, false));
33✔
75
  });
11✔
76
}
11✔
77

78
TEST_CASE("fit benchmark", "[.benchmark]") {
×
79
  prepare_and_run_bench([](const arma::colvec& y, const arma::mat& X, int i) {
×
80
    Kriging ok = Kriging("gauss");
×
81
    BENCHMARK("Kriging::fit#" + std::to_string(i)) {
×
82
      Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
×
83
      return ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS", "LL", parameters);  // FIXME no move
×
84
    };
×
85
  });
×
86
}
×
87

88
TEST_CASE("logLikelihoodFun benchmark", "[.benchmark]") {
×
89
  prepare_and_run_bench([](const arma::colvec& y, const arma::mat& X, int i) {
×
90
    Kriging ok = Kriging("gauss");
×
91
    ok.fit(y, X);  // FIXME no move
×
92

93
    const double theta = 0.5;
×
94
    arma::vec theta_vec(X.n_cols);
×
95
    theta_vec.fill(theta);
×
96

97
    BENCHMARK("Kriging::logLikelihoodFun#" + std::to_string(i)) {
×
98
      return std::get<0>(ok.logLikelihoodFun(theta_vec, false, false, false));  //
×
99
    };
×
100
  });
×
101
}
×
102

103
TEST_CASE("logLikelihoodGrad benchmark", "[.benchmark]") {
×
104
  prepare_and_run_bench([](const arma::colvec& y, const arma::mat& X, int i) {
×
105
    Kriging ok = Kriging("gauss");
×
106
    ok.fit(y, X);  // FIXME no move
×
107

108
    const double theta = 0.5;
×
109
    arma::vec theta_vec(X.n_cols);
×
110
    theta_vec.fill(theta);
×
111

112
    BENCHMARK("Kriging::logLikelihoodGrad#" + std::to_string(i)) {
×
113
      return std::get<1>(ok.logLikelihoodFun(theta_vec, true, false, false));  //
×
114
    };
×
115
  });
×
NEW
116
}
×
117

118
// Branin function for testing
119
auto branin = [](const arma::rowvec& x) {
100✔
120
  double x1 = x(0) * 15.0 - 5.0;
100✔
121
  double x2 = x(1) * 15.0;
100✔
122
  double pi = M_PI;
100✔
123
  double term1 = x2 - 5.0 / (4.0 * pi * pi) * (x1 * x1) + 5.0 / pi * x1 - 6.0;
100✔
124
  return term1 * term1 + 10.0 * (1.0 - 1.0 / (8.0 * pi)) * std::cos(x1) + 10.0;
100✔
125
};
126

127
TEST_CASE("Branin BFGS") {
5✔
128
  arma::arma_rng::set_seed(42);
5✔
129

130
  // Generate 20 uniformly distributed points in [0,1]^2
131
  const arma::uword n = 20;
5✔
132
  const arma::uword d = 2;
5✔
133
  arma::mat X(n, d, arma::fill::randu);
5✔
134

135
  // Compute Branin function values
136
  arma::colvec y(n);
5✔
137
  for (arma::uword k = 0; k < n; ++k) {
105✔
138
    y(k) = branin(X.row(k));
200✔
139
  }
140

141
  SECTION("BFGS single-start") {
5✔
142
    Kriging ok = Kriging("gauss");
2✔
143
    Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
1✔
144
    ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS", "LL", parameters);
1✔
145

146
    // Check that the model was fitted successfully
147
    CHECK(ok.theta().n_elem == d);
1✔
148
    CHECK(ok.sigma2() > 0);
1✔
149

150
    // Test prediction on a new point
151
    arma::mat X_new(1, d);
1✔
152
    X_new(0, 0) = 0.5;
1✔
153
    X_new(0, 1) = 0.5;
1✔
154
    auto pred = ok.predict(X_new, true, false, false);
1✔
155
    CHECK(std::get<0>(pred).n_elem == 1);
1✔
156
    CHECK(std::get<1>(pred).n_elem == 1);
1✔
157
  }
6✔
158

159
  SECTION("BFGS20 multistart") {
5✔
160
    Kriging ok = Kriging("gauss");
2✔
161
    Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
1✔
162
    ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS20", "LL", parameters);
1✔
163

164
    // Check that the model was fitted successfully
165
    CHECK(ok.theta().n_elem == d);
1✔
166
    CHECK(ok.sigma2() > 0);
1✔
167

168
    // Test prediction on a new point
169
    arma::mat X_new(1, d);
1✔
170
    X_new(0, 0) = 0.5;
1✔
171
    X_new(0, 1) = 0.5;
1✔
172
    auto pred = ok.predict(X_new, true, false, false);
1✔
173
    CHECK(std::get<0>(pred).n_elem == 1);
1✔
174
    CHECK(std::get<1>(pred).n_elem == 1);
1✔
175
  }
6✔
176

177
  SECTION("BFGS vs BFGS20 comparison") {
5✔
178
    // Fit with single-start BFGS
179
    Kriging ok_single = Kriging("gauss");
2✔
180
    Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
1✔
181
    ok_single.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS", "LL", parameters);
1✔
182
    double ll_single = ok_single.logLikelihood();
1✔
183

184
    // Fit with multi-start BFGS20
185
    Kriging ok_multi = Kriging("gauss");
2✔
186
    ok_multi.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS20", "LL", parameters);
1✔
187
    double ll_multi = ok_multi.logLikelihood();
1✔
188

189
    // Multi-start should find at least as good solution as single-start
190
    INFO("Single-start log-likelihood: " << ll_single);
1✔
191
    INFO("Multi-start log-likelihood: " << ll_multi);
1✔
192

193
    // Check and display OpenMP status
194
#ifdef _OPENMP
195
    INFO("OpenMP is ENABLED - parallel execution active");
1✔
196
#else
197
    INFO("OpenMP is DISABLED - sequential execution only");
198
    WARN("Enable OpenMP for parallel speedup: configure with -DCMAKE_CXX_FLAGS=\"-fopenmp\"");
199
#endif
200

201
    CHECK(ll_multi >= ll_single - std::abs(ll_single) * 1e-6);  // Allow small numerical tolerance (relative)
1✔
202
  }
6✔
203

204
  SECTION("BFGS10 vs BFGS20 timing (parallel efficiency)") {
5✔
205
    using namespace std::chrono;
206

207
    const int n_runs = 3;
1✔
208
    std::vector<double> times10, times20;
1✔
209

210
    INFO("Running " << n_runs << " iterations of each test...");
1✔
211

212
    // Run multiple times to get better statistics
213
    for (int run = 0; run < n_runs; ++run) {
4✔
214
      // Time BFGS10
215
      auto start10 = high_resolution_clock::now();
3✔
216
      Kriging ok10 = Kriging("gauss");
6✔
217
      Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
3✔
218
      ok10.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS10", "LL", parameters);
3✔
219
      auto end10 = high_resolution_clock::now();
3✔
220
      double time10 = duration_cast<milliseconds>(end10 - start10).count();
3✔
221
      times10.push_back(time10);
3✔
222

223
      // Time BFGS20
224
      auto start20 = high_resolution_clock::now();
3✔
225
      Kriging ok20 = Kriging("gauss");
6✔
226
      ok20.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS20", "LL", parameters);
3✔
227
      auto end20 = high_resolution_clock::now();
3✔
228
      double time20 = duration_cast<milliseconds>(end20 - start20).count();
3✔
229
      times20.push_back(time20);
3✔
230

231
      INFO("Run " << (run + 1) << ": BFGS10=" << time10 << "ms, BFGS20=" << time20 << "ms");
3✔
232
    }
3✔
233

234
    // Calculate averages
235
    double avg10 = 0.0, avg20 = 0.0;
1✔
236
    for (int i = 0; i < n_runs; ++i) {
4✔
237
      avg10 += times10[i];
3✔
238
      avg20 += times20[i];
3✔
239
    }
240
    avg10 /= n_runs;
1✔
241
    avg20 /= n_runs;
1✔
242

243
    double ratio = (avg10 > 0) ? (avg20 / avg10) : 0.0;
1✔
244

245
    INFO("Average BFGS10 time: " << avg10 << " ms");
1✔
246
    INFO("Average BFGS20 time: " << avg20 << " ms");
1✔
247
    INFO("Average ratio (time20/time10): " << ratio);
1✔
248

249
    // With effective parallelization, BFGS20 should take similar time to BFGS10
250
    // The ratio should be close to 1.0, certainly less than 1.5
251
    // Without parallelization, the ratio would be close to 2.0
252
    if (ratio < 1.3) {
1✔
NEW
253
      INFO("Ratio < 1.3: Parallelization is working effectively!");
×
254
    } else if (ratio < 1.7) {
1✔
NEW
255
      INFO("Ratio between 1.3-1.7: Partial parallelization or overhead");
×
NEW
256
    } else {
×
257
      INFO("Ratio >= 1.7: Likely sequential execution (no parallelization)");
1✔
258
    }
1✔
259

260
    // Basic sanity checks
261
    CHECK(avg10 > 0);
1✔
262
    CHECK(avg20 > 0);
1✔
263
    CHECK(ratio > 0);
1✔
264
  }
6✔
265

266
  SECTION("BFGS20 vs 20×BFGS1 equivalence (given theta0)") {
5✔
267
    // Test that BFGS20 with a specific theta0 matrix gives the same result
268
    // as running 20 separate BFGS1 optimizations, one for each row of theta0
269
    
270
    INFO("Testing that BFGS20 with given theta0 equals 20 separate BFGS1 runs");
1✔
271
    
272
    // Create a specific theta0 matrix with 20 starting points
273
    arma::mat theta0(20, d);
1✔
274
    arma::arma_rng::set_seed(12345);  // Fixed seed for reproducibility
1✔
275
    for (arma::uword i = 0; i < 20; i++) {
21✔
276
      theta0.row(i) = arma::randu<arma::rowvec>(d) % (arma::ones<arma::rowvec>(d) * 2.0) + 0.01;
60✔
277
    }
278
    
279
    INFO("Created theta0 matrix: " << theta0.n_rows << " x " << theta0.n_cols);
1✔
280
    
281
    // Run BFGS20 with this theta0
282
    INFO("Running BFGS20...");
1✔
283
    Kriging ok_bfgs20 = Kriging("gauss");
2✔
284
    Kriging::Parameters params_multi{std::nullopt, false, theta0, true, std::nullopt, true};
1✔
285
    ok_bfgs20.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS20", "LL", params_multi);
1✔
286
    
287
    arma::vec theta_bfgs20 = ok_bfgs20.theta();
1✔
288
    double sigma2_bfgs20 = ok_bfgs20.sigma2();
1✔
289
    double ll_bfgs20 = ok_bfgs20.logLikelihood();
1✔
290
    
291
    INFO("BFGS20 final result: theta=" << theta_bfgs20.t() << ", sigma2=" << sigma2_bfgs20 << ", LL=" << ll_bfgs20);
3✔
292
    
293
    // Run 20 separate BFGS1 optimizations, one for each row of theta0
294
    std::vector<double> ll_vec(20);
1✔
295
    std::vector<arma::vec> theta_vec(20);
1✔
296
    std::vector<double> sigma2_vec(20);
1✔
297
    
298
    INFO("Running 20 separate BFGS1 optimizations...");
1✔
299
    
300
    // Important: Set seed again before BFGS1 runs to match conditions
301
    arma::arma_rng::set_seed(12345);
1✔
302
    
303
    for (arma::uword i = 0; i < 20; i++) {
21✔
304
      Kriging ok_bfgs1 = Kriging("gauss");
40✔
305
      arma::mat theta0_i = theta0.row(i);  // Extract row i as 1×d matrix
20✔
306
      Kriging::Parameters params_single{std::nullopt, false, theta0_i, true, std::nullopt, true};
20✔
307
      ok_bfgs1.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS", "LL", params_single);
20✔
308
      
309
      theta_vec[i] = ok_bfgs1.theta();
20✔
310
      sigma2_vec[i] = ok_bfgs1.sigma2();
20✔
311
      ll_vec[i] = ok_bfgs1.logLikelihood();
20✔
312
    }
20✔
313
    
314
    INFO("Completed all 20 BFGS1 runs");
1✔
315
    
316
    // Find the best result from the 20 BFGS1 runs
317
    auto best_iter = std::max_element(ll_vec.begin(), ll_vec.end());
1✔
318
    int best_idx = std::distance(ll_vec.begin(), best_iter);
1✔
319
    double ll_best_bfgs1 = ll_vec[best_idx];
1✔
320
    arma::vec theta_best_bfgs1 = theta_vec[best_idx];
1✔
321
    double sigma2_best_bfgs1 = sigma2_vec[best_idx];
1✔
322
    
323
    INFO("Best BFGS1[" << best_idx << "]: theta=" << theta_best_bfgs1.t() 
3✔
324
         << ", sigma2=" << sigma2_best_bfgs1 << ", LL=" << ll_best_bfgs1);
325
    
326
    // Compare BFGS20 result with best BFGS1 result
327
    // They should be very close (within numerical tolerance)
328
    double theta_diff = arma::norm(theta_bfgs20 - theta_best_bfgs1, 2);
1✔
329
    double theta_norm = arma::norm(theta_best_bfgs1, 2);
1✔
330
    double sigma2_diff = std::abs(sigma2_bfgs20 - sigma2_best_bfgs1);
1✔
331
    double ll_diff = std::abs(ll_bfgs20 - ll_best_bfgs1);
1✔
332
    
333
    INFO("Differences:");
1✔
334
    INFO("  ||theta_BFGS20 - theta_best_BFGS1||_2 = " << theta_diff);
1✔
335
    INFO("  |sigma2_BFGS20 - sigma2_best_BFGS1| = " << sigma2_diff);
1✔
336
    INFO("  |LL_BFGS20 - LL_best_BFGS1| = " << ll_diff);
1✔
337
    
338
    // Check for EXACT equivalence
339
    // BFGS20 and 20×BFGS1 should give identical results (same algorithm, same starting points)
340
    // With proper thread-local optimization and staggered startup, results should be deterministic
341
    CHECK(theta_diff / theta_norm <= 1e-3);  // Theta must be exactly the same (relative)
1✔
342
    CHECK(sigma2_diff / std::abs(sigma2_best_bfgs1) <= 1e-3); // Sigma2 must be exactly the same (relative)
1✔
343
    CHECK(ll_diff / std::abs(ll_best_bfgs1) <= 1e-3);     // Log-likelihood must be exactly the same (relative)
1✔
344
    
345
    INFO("✓ BFGS20 and best of 20×BFGS1 are EXACTLY equivalent!");
1✔
346
  }
6✔
347
}
5✔
348

349
TEST_CASE("Kriging fit with given parameters - BFGS1") {
1✔
350
  arma::arma_rng::set_seed(123);
1✔
351
  const arma::uword n = 20;
1✔
352
  const arma::uword d = 2;
1✔
353

354
  arma::mat X(n, d, arma::fill::randu);
1✔
355
  arma::colvec y(n);
1✔
356
  for (arma::uword k = 0; k < n; ++k)
21✔
357
    y(k) = f(X.row(k));
40✔
358

359
  // Define specific starting parameters
360
  arma::mat theta_start(1, d);
1✔
361
  theta_start(0, 0) = 0.5;
1✔
362
  theta_start(0, 1) = 0.3;
1✔
363
  double sigma2_start = 0.1;
1✔
364

365
  Kriging ok = Kriging("gauss");
2✔
366
  // Provide starting values for sigma2 and theta, optimize all
367
  Kriging::Parameters parameters{sigma2_start, true, theta_start, true, std::nullopt, true};
1✔
368
  ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS", "LL", parameters);
1✔
369

370
  // Check that optimization ran (parameters should be different from starting values)
371
  CHECK(ok.theta().n_elem == d);
1✔
372
  CHECK(ok.sigma2() > 0);
1✔
373
  
374
  // Verify predictions work
375
  arma::mat X_new(1, d);
1✔
376
  X_new.fill(0.5);
1✔
377
  auto pred = ok.predict(X_new, true, false, false);
1✔
378
  CHECK(std::get<0>(pred).n_elem == 1);
1✔
379
  CHECK(std::get<1>(pred).n_elem == 1);
1✔
380
}
1✔
381

382
TEST_CASE("Kriging fit with given parameters - BFGS20") {
1✔
383
  arma::arma_rng::set_seed(123);
1✔
384
  const arma::uword n = 20;
1✔
385
  const arma::uword d = 2;
1✔
386

387
  arma::mat X(n, d, arma::fill::randu);
1✔
388
  arma::colvec y(n);
1✔
389
  for (arma::uword k = 0; k < n; ++k)
21✔
390
    y(k) = f(X.row(k));
40✔
391

392
  // Define specific theta starting points for multistart
393
  arma::mat theta_starts(20, d);
1✔
394
  for (arma::uword i = 0; i < 20; i++) {
21✔
395
    theta_starts.row(i) = arma::randu<arma::rowvec>(d) * 2.0 + 0.1;
40✔
396
  }
397
  double sigma2_given = 0.1;
1✔
398

399
  Kriging ok = Kriging("gauss");
2✔
400
  Kriging::Parameters parameters{sigma2_given, false, theta_starts, true, std::nullopt, true};
1✔
401
  ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS20", "LL", parameters);
1✔
402

403
  // Check that sigma2 was kept fixed
404
  CHECK(ok.sigma2() == sigma2_given);
1✔
405
  // Theta should be optimized (one of the starting points)
406
  CHECK(ok.theta().n_elem == d);
1✔
407

408
  // Verify predictions work
409
  arma::mat X_new(1, d);
1✔
410
  X_new.fill(0.5);
1✔
411
  auto pred = ok.predict(X_new, true, false, false);
1✔
412
  CHECK(std::get<0>(pred).n_elem == 1);
1✔
413
  CHECK(std::get<1>(pred).n_elem == 1);
1✔
414
}
1✔
415

416
TEST_CASE("Kriging all parameter combinations", "[multistart]") {
11✔
417
  arma::arma_rng::set_seed(123);
11✔
418
  const arma::uword n = 20;
11✔
419
  const arma::uword d = 2;
11✔
420

421
  arma::mat X(n, d, arma::fill::randu);
11✔
422
  arma::colvec y(n);
11✔
423
  for (arma::uword k = 0; k < n; ++k)
231✔
424
    y(k) = f(X.row(k));
440✔
425

426
  // Pre-defined parameter values
427
  double sigma2_val = 0.1;
11✔
428
  arma::mat theta_val(1, d);
11✔
429
  theta_val.fill(0.5);
11✔
430
  arma::mat theta_starts(20, d);
11✔
431
  for (arma::uword i = 0; i < 20; i++) {
231✔
432
    theta_starts.row(i) = arma::randu<arma::rowvec>(d) * 2.0 + 0.1;
440✔
433
  }
434

435
  // Test all combinations of parameter estimation flags with different optimizers
436
  std::vector<std::string> optims = {"none", "BFGS", "BFGS20"};
66✔
437
  
438
  for (const auto& optim : optims) {
44✔
439
    DYNAMIC_SECTION("Optim: " << optim) {
33✔
440
      // Combination 1: Estimate all parameters (not valid for "none")
441
      if (optim != "none") {
11✔
442
        SECTION("Estimate all (sigma2, theta, beta)") {
9✔
443
          Kriging kr("gauss");
4✔
444
          Kriging::Parameters params{std::nullopt, true, std::nullopt, true, std::nullopt, true};
2✔
445
          kr.fit(y, X, Trend::RegressionModel::Constant, false, optim, "LL", params);
2✔
446
          CHECK(kr.sigma2() > 0);
2✔
447
          CHECK(kr.theta().n_elem == d);
2✔
448
        }
11✔
449
      }
450

451
      // Combination 2: Fix sigma2, estimate theta and beta (not valid for "none")
452
      if (optim != "none") {
11✔
453
        SECTION("Fix sigma2, estimate theta and beta") {
9✔
454
          Kriging kr("gauss");
4✔
455
          Kriging::Parameters params{sigma2_val, false, std::nullopt, true, std::nullopt, true};
2✔
456
          kr.fit(y, X, Trend::RegressionModel::Constant, false, optim, "LL", params);
2✔
457
          CHECK(kr.sigma2() == sigma2_val);
2✔
458
          CHECK(kr.theta().n_elem == d);
2✔
459
        }
11✔
460
      }
461

462
      // Combination 3: Estimate sigma2, fix theta, estimate beta (only "none" truly fixes theta)
463
      SECTION("Estimate sigma2, fix theta, estimate beta") {
11✔
464
        Kriging kr("gauss");
6✔
465
        Kriging::Parameters params{std::nullopt, true, theta_val, false, std::nullopt, true};
3✔
466
        kr.fit(y, X, Trend::RegressionModel::Constant, false, optim, "LL", params);
3✔
467
        CHECK(kr.sigma2() > 0);
3✔
468
        if (optim == "none") {
3✔
469
          CHECK(arma::approx_equal(kr.theta(), theta_val.t(), "absdiff", 1e-10));
2✔
470
        } else {
471
          // BFGS variants use theta as starting point even when is_theta_estim=false
472
          CHECK(kr.theta().n_elem == d);
2✔
473
        }
474
      }
14✔
475

476
      // Combination 4: Fix both sigma2 and theta, estimate beta (only "none" truly fixes theta)
477
      SECTION("Fix sigma2 and theta, estimate beta") {
11✔
478
        Kriging kr("gauss");
6✔
479
        Kriging::Parameters params{sigma2_val, false, theta_val, false, std::nullopt, true};
3✔
480
        kr.fit(y, X, Trend::RegressionModel::Constant, false, optim, "LL", params);
3✔
481
        CHECK(kr.sigma2() == sigma2_val);
3✔
482
        if (optim == "none") {
3✔
483
          CHECK(arma::approx_equal(kr.theta(), theta_val.t(), "absdiff", 1e-10));
2✔
484
        } else {
485
          // BFGS variants use theta as starting point even when is_theta_estim=false
486
          CHECK(kr.theta().n_elem == d);
2✔
487
        }
488
      }
14✔
489

490
      // Combination 5: Provide starting values for sigma2 and theta (BFGS20 multistart only)
491
      if (optim == "BFGS20") {
11✔
492
        SECTION("Multistart with theta starting points") {
5✔
493
          Kriging kr("gauss");
2✔
494
          Kriging::Parameters params{sigma2_val, true, theta_starts, true, std::nullopt, true};
1✔
495
          kr.fit(y, X, Trend::RegressionModel::Constant, false, optim, "LL", params);
1✔
496
          CHECK(kr.sigma2() > 0);
1✔
497
          CHECK(kr.theta().n_elem == d);
1✔
498
        }
6✔
499
      }
500
    }
33✔
501
  }
502

503
  // Verify predictions work for a representative case
504
  Kriging kr_final("gauss");
22✔
505
  Kriging::Parameters params_final{std::nullopt, true, std::nullopt, true, std::nullopt, true};
11✔
506
  kr_final.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS", "LL", params_final);
11✔
507
  
508
  arma::mat X_new(1, d);
11✔
509
  X_new.fill(0.5);
11✔
510
  auto pred = kr_final.predict(X_new, true, false, false);
11✔
511
  CHECK(std::get<0>(pred).n_elem == 1);
11✔
512
  CHECK(std::get<1>(pred).n_elem == 1);
11✔
513
}
11✔
514

NEW
515
TEST_CASE("Kriging intensive stress test", "[intensive][multistart]") {
×
NEW
516
  arma::arma_rng::set_seed(789);
×
517
  
NEW
518
  SECTION("Many iterations with BFGS20") {
×
NEW
519
    const int n_iterations = 50;
×
NEW
520
    const arma::uword n = 30;
×
NEW
521
    const arma::uword d = 2;
×
NEW
522
    int failure_count = 0;
×
523

NEW
524
    INFO("Running " << n_iterations << " intensive iterations with BFGS20");
×
525
    
NEW
526
    for (int iter = 0; iter < n_iterations; ++iter) {
×
NEW
527
      arma::mat X = arma::randu<arma::mat>(n, d);
×
NEW
528
      arma::colvec y = arma::sin(5.0 * X.col(0)) % arma::cos(3.0 * X.col(1));
×
NEW
529
      y += 0.1 * arma::randn(n);
×
530

NEW
531
      Kriging ok = Kriging("gauss");
×
NEW
532
      Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
×
533
      
534
      try {
NEW
535
        ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS20", "LL", parameters);
×
536
        
537
        // Check if fit produced invalid results
NEW
538
        if (!std::isfinite(ok.sigma2()) || ok.sigma2() <= 0) {
×
NEW
539
          failure_count++;
×
NEW
540
          std::stringstream X_filename, y_filename;
×
NEW
541
          X_filename << "failing_kriging_X_iter" << iter << "_failure" << failure_count << ".csv";
×
NEW
542
          y_filename << "failing_kriging_y_iter" << iter << "_failure" << failure_count << ".csv";
×
543
          
NEW
544
          X.save(X_filename.str(), arma::csv_ascii);
×
NEW
545
          y.save(y_filename.str(), arma::csv_ascii);
×
546
          
NEW
547
          INFO("Saved failing case " << failure_count << " (iteration " << iter << "):");
×
NEW
548
          INFO("  X saved to: " << X_filename.str());
×
NEW
549
          INFO("  y saved to: " << y_filename.str());
×
NEW
550
          INFO("  sigma2 = " << ok.sigma2());
×
551
          
552
          // Still report the failure for test tracking
NEW
553
          CHECK(ok.sigma2() > 0);
×
NEW
554
        } else {
×
NEW
555
          CHECK(ok.theta().n_elem == d);
×
NEW
556
          CHECK(ok.sigma2() > 0);
×
557
          
558
          // Prediction test
NEW
559
          arma::mat X_new = arma::randu<arma::mat>(5, d);
×
NEW
560
          auto pred = ok.predict(X_new, true, false, false);
×
NEW
561
          CHECK(std::get<0>(pred).n_elem == 5);
×
NEW
562
        }
×
563
        
NEW
564
        if ((iter + 1) % 10 == 0) {
×
NEW
565
          INFO("Completed iteration " << (iter + 1) << "/" << n_iterations 
×
566
               << " (" << failure_count << " failures so far)");
NEW
567
        }
×
NEW
568
      } catch (const std::exception& e) {
×
NEW
569
        FAIL("Exception at iteration " << (iter + 1) << ": " << e.what());
×
NEW
570
      }
×
NEW
571
    }
×
572
    
NEW
573
    INFO("Total failures: " << failure_count << " out of " << n_iterations);
×
NEW
574
  }
×
575

NEW
576
  SECTION("Large dataset with multiple starts") {
×
NEW
577
    const arma::uword n = 100;
×
NEW
578
    const arma::uword d = 3;
×
579

NEW
580
    arma::mat X = arma::randu<arma::mat>(n, d);
×
NEW
581
    arma::colvec y = arma::sin(4.0 * X.col(0)) % arma::cos(3.0 * X.col(1)) % arma::exp(-2.0 * X.col(2));
×
NEW
582
    y += 0.05 * arma::randn(n);
×
583

NEW
584
    INFO("Testing large dataset (n=" << n << ", d=" << d << ") with BFGS20");
×
585
    
NEW
586
    Kriging ok = Kriging("gauss");
×
NEW
587
    Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
×
588
    
NEW
589
    ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS20", "LL", parameters);
×
590
    
NEW
591
    CHECK(ok.theta().n_elem == d);
×
NEW
592
    CHECK(ok.sigma2() > 0);
×
593
    
594
    // Large prediction batch
NEW
595
    arma::mat X_new = arma::randu<arma::mat>(50, d);
×
NEW
596
    auto pred = ok.predict(X_new, true, true, true);
×
NEW
597
    CHECK(std::get<0>(pred).n_elem == 50);
×
NEW
598
    CHECK(std::get<1>(pred).n_elem == 50);
×
NEW
599
    CHECK(std::get<2>(pred).n_rows == 50);
×
NEW
600
  }
×
601

NEW
602
  SECTION("Rapid sequential BFGS20 calls") {
×
NEW
603
    const int n_rapid = 20;
×
NEW
604
    const arma::uword n = 25;
×
NEW
605
    const arma::uword d = 2;
×
606

NEW
607
    INFO("Running " << n_rapid << " rapid sequential BFGS20 fits");
×
608
    
NEW
609
    for (int i = 0; i < n_rapid; ++i) {
×
NEW
610
      arma::mat X = arma::randu<arma::mat>(n, d);
×
NEW
611
      arma::colvec y = arma::sin(5.0 * X.col(0) + 3.0 * X.col(1));
×
NEW
612
      y += 0.05 * arma::randn(n);
×
613

NEW
614
      Kriging ok = Kriging("gauss");
×
NEW
615
      Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
×
NEW
616
      ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS20", "LL", parameters);
×
617
      
NEW
618
      CHECK(ok.sigma2() > 0);
×
NEW
619
      CHECK(ok.theta().n_elem == d);
×
NEW
620
    }
×
621
    
NEW
622
    INFO("Completed all " << n_rapid << " rapid sequential fits successfully");
×
NEW
623
  }
×
624

NEW
625
  SECTION("Different kernel types with BFGS20") {
×
NEW
626
    const arma::uword n = 30;
×
NEW
627
    const arma::uword d = 2;
×
NEW
628
    arma::mat X = arma::randu<arma::mat>(n, d);
×
NEW
629
    arma::colvec y = arma::sin(5.0 * X.col(0)) % arma::cos(3.0 * X.col(1));
×
NEW
630
    y += 0.1 * arma::randn(n);
×
631

NEW
632
    std::vector<std::string> kernels = {"gauss", "exp", "matern3_2", "matern5_2"};
×
633
    
NEW
634
    for (const auto& kernel : kernels) {
×
NEW
635
      INFO("Testing kernel: " << kernel);
×
636
      
NEW
637
      Kriging ok = Kriging(kernel);
×
NEW
638
      Kriging::Parameters parameters{std::nullopt, true, std::nullopt, true, std::nullopt, true};
×
639
      
NEW
640
      ok.fit(y, X, Trend::RegressionModel::Constant, false, "BFGS20", "LL", parameters);
×
641
      
NEW
642
      CHECK(ok.theta().n_elem == d);
×
NEW
643
      CHECK(ok.sigma2() > 0);
×
NEW
644
    }
×
NEW
645
  }
×
UNCOV
646
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc