21#if defined(ENABLE_CATCH)
22# include <catch2/catch_all.hpp>
34constexpr double kPi = 3.141592653589793238462643383279502884;
41TEST_CASE(
"AxisTransform linear round-trip",
"[SVDComponents][Region][axis_transform]") {
42 const auto t = cp_region::AxisTransform::make(cp_region::AxisScale::LINEAR, 1.5, 9.5);
44 std::uniform_real_distribution<double> u(1.5, 9.5);
45 for (
int k = 0; k < 100; ++k) {
46 const double a = u(rng);
47 const double xi = t.forward(a);
48 REQUIRE(xi >= -1e-15);
49 REQUIRE(xi <= 1.0 + 1e-15);
50 const double a_back = t.inverse(xi);
51 REQUIRE(std::abs(a_back - a) / a < 1e-14);
55TEST_CASE(
"AxisTransform log round-trip",
"[SVDComponents][Region][axis_transform]") {
56 const auto t = cp_region::AxisTransform::make(cp_region::AxisScale::LOG, 1e-3, 1e3);
58 std::uniform_real_distribution<double> u(std::log(1e-3), std::log(1e3));
59 for (
int k = 0; k < 100; ++k) {
60 const double a = std::exp(u(rng));
61 const double xi = t.forward(a);
62 REQUIRE(xi >= -1e-15);
63 REQUIRE(xi <= 1.0 + 1e-15);
64 const double a_back = t.inverse(xi);
65 REQUIRE(std::abs(a_back - a) / a < 1e-14);
69TEST_CASE(
"AxisTransform analytic Jacobian",
"[SVDComponents][Region][axis_transform]") {
70 const auto t_lin = cp_region::AxisTransform::make(cp_region::AxisScale::LINEAR, 2.0, 10.0);
71 REQUIRE(t_lin.dxi_da(5.0) == Catch::Approx(1.0 / 8.0).margin(1e-15));
73 const auto t_log = cp_region::AxisTransform::make(cp_region::AxisScale::LOG, 1.0, 100.0);
75 const double a = 10.0;
76 const double fd = (t_log.forward(a * (1 + 1e-7)) - t_log.forward(a * (1 - 1e-7))) / (2.0 * a * 1e-7);
77 REQUIRE(t_log.dxi_da(a) == Catch::Approx(fd).epsilon(1e-6));
80TEST_CASE(
"AxisTransform power round-trip",
"[SVDComponents][Region][axis_transform]") {
83 const double a_lo = 0.9 * 3.651e6;
84 const double a_hi = (1.0 - 1e-6) * 3.651e6;
85 const auto t = cp_region::AxisTransform::make(cp_region::AxisScale::POWER, a_lo, a_hi);
87 std::uniform_real_distribution<double> u(a_lo, a_hi);
88 for (
int k = 0; k < 100; ++k) {
89 const double a = u(rng);
90 const double xi = t.forward(a);
91 REQUIRE(xi >= -1e-12);
92 REQUIRE(xi <= 1.0 + 1e-12);
93 const double a_back = t.inverse(xi);
94 REQUIRE(std::abs(a_back - a) / a < 1e-12);
97 REQUIRE(t.forward(a_lo) == Catch::Approx(0.0).margin(1e-15));
98 REQUIRE(t.forward(a_hi) == Catch::Approx(1.0).margin(1e-15));
99 REQUIRE(t.inverse(0.0) == Catch::Approx(a_lo).margin(1e-15));
100 REQUIRE(t.inverse(1.0) == Catch::Approx(a_hi).margin(1e-15));
104 const double a_mid = t.inverse(0.5);
105 const double frac = (a_mid - a_lo) / (a_hi - a_lo);
106 REQUIRE(frac == Catch::Approx(0.875).margin(1e-15));
109 REQUIRE_THROWS_AS(cp_region::AxisTransform::make(cp_region::AxisScale::POWER, 1.0, 1.0), std::invalid_argument);
112TEST_CASE(
"AxisTransform power Jacobian vs central FD",
"[SVDComponents][Region][axis_transform]") {
115 const auto t = cp_region::AxisTransform::make(cp_region::AxisScale::POWER, 1.0, 2.0);
116 for (
double a : {1.1, 1.3, 1.5, 1.7, 1.9}) {
117 const double h = 1e-7 * a;
118 const double fd = (t.forward(a + h) - t.forward(a - h)) / (2.0 * h);
119 REQUIRE(t.dxi_da(a) == Catch::Approx(fd).epsilon(1e-5));
123TEST_CASE(
"AxisTransform power_lo round-trip",
"[SVDComponents][Region][axis_transform]") {
126 const double a_lo = (1.0 + 1e-10) * 3.651e6;
127 const double a_hi = 1.1 * 3.651e6;
128 const auto t = cp_region::AxisTransform::make(cp_region::AxisScale::POWER_LO, a_lo, a_hi);
130 std::uniform_real_distribution<double> u(a_lo, a_hi);
131 for (
int k = 0; k < 100; ++k) {
132 const double a = u(rng);
133 const double xi = t.forward(a);
134 REQUIRE(xi >= -1e-12);
135 REQUIRE(xi <= 1.0 + 1e-12);
136 const double a_back = t.inverse(xi);
137 REQUIRE(std::abs(a_back - a) / a < 1e-12);
140 REQUIRE(t.forward(a_lo) == Catch::Approx(0.0).margin(1e-15));
141 REQUIRE(t.forward(a_hi) == Catch::Approx(1.0).margin(1e-15));
142 REQUIRE(t.inverse(0.0) == Catch::Approx(a_lo).margin(1e-15));
143 REQUIRE(t.inverse(1.0) == Catch::Approx(a_hi).margin(1e-15));
146 const double a_mid = t.inverse(0.5);
147 const double frac = (a_mid - a_lo) / (a_hi - a_lo);
148 REQUIRE(frac == Catch::Approx(0.125).margin(1e-15));
151TEST_CASE(
"AxisTransform power_lo Jacobian vs central FD",
"[SVDComponents][Region][axis_transform]") {
153 const auto t = cp_region::AxisTransform::make(cp_region::AxisScale::POWER_LO, 1.0, 2.0);
154 for (
double a : {1.1, 1.3, 1.5, 1.7, 1.9}) {
155 const double h = 1e-7 * a;
156 const double fd = (t.forward(a + h) - t.forward(a - h)) / (2.0 * h);
157 REQUIRE(t.dxi_da(a) == Catch::Approx(fd).epsilon(1e-5));
165TEST_CASE(
"ConstantCurve eval / eval_da / bounds",
"[SVDComponents][Region][boundary_curve_constant]") {
166 cp_region::ConstantCurve c(0.0, 5.0, 3.14);
167 REQUIRE(c.eval(2.7) == 3.14);
168 REQUIRE(c.eval_da(2.7) == 0.0);
169 REQUIRE(c.bounds().first == 3.14);
170 REQUIRE(c.bounds().second == 3.14);
171 REQUIRE(c.a_range().first == 0.0);
172 REQUIRE(c.a_range().second == 5.0);
179TEST_CASE(
"PiecewiseChebyshevCurve fits a smooth function (LINEAR axis)",
"[SVDComponents][Region][boundary_curve_cheb]") {
180 const auto f = [](
double a) {
return std::sin(a) + 0.5 * a; };
182 cp_region::PiecewiseChebyshevCurve::build(0.5, 5.5, 4, 12, cp_region::PiecewiseChebyshevCurve::ParamScale::LINEAR, f);
185 std::uniform_real_distribution<double> u(0.6, 5.4);
186 for (
int k = 0; k < 100; ++k) {
187 const double a = u(rng);
188 REQUIRE(curve->eval(a) == Catch::Approx(f(a)).epsilon(1e-10));
190 const double h = 1e-6;
191 const double fd = (curve->eval(a + h) - curve->eval(a - h)) / (2.0 * h);
192 REQUIRE(curve->eval_da(a) == Catch::Approx(fd).epsilon(1e-6));
196TEST_CASE(
"PiecewiseChebyshevCurve fits a smooth function (LOG axis)",
"[SVDComponents][Region][boundary_curve_cheb]") {
198 const auto f = [](
double a) {
return std::log(a) * std::log(a); };
200 cp_region::PiecewiseChebyshevCurve::build(1.0, 1e4, 3, 10, cp_region::PiecewiseChebyshevCurve::ParamScale::LOG, f);
203 std::uniform_real_distribution<double> u(std::log(1.05), std::log(1e4 * 0.99));
204 for (
int k = 0; k < 100; ++k) {
205 const double a = std::exp(u(rng));
206 REQUIRE(curve->eval(a) == Catch::Approx(f(a)).epsilon(1e-8));
207 const double h = a * 1e-6;
208 const double fd = (curve->eval(a + h) - curve->eval(a - h)) / (2.0 * h);
209 REQUIRE(curve->eval_da(a) == Catch::Approx(fd).epsilon(1e-5));
217TEST_CASE(
"CubicSplineCurve through known knots",
"[SVDComponents][Region][boundary_curve_spline]") {
219 std::vector<double> x = {0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0};
220 std::vector<double> y;
222 for (
double xi : x) {
223 y.push_back(xi * xi);
225 auto curve = cp_region::CubicSplineCurve::build(x, y);
228 for (std::size_t i = 0; i < x.size(); ++i) {
229 REQUIRE(curve->eval(x[i]) == Catch::Approx(y[i]).margin(1e-13));
232 std::mt19937 rng(11);
233 std::uniform_real_distribution<double> u(0.05, 2.95);
234 for (
int k = 0; k < 50; ++k) {
235 const double a = u(rng);
236 const double h = 1e-6;
237 const double fd = (curve->eval(a + h) - curve->eval(a - h)) / (2.0 * h);
238 REQUIRE(curve->eval_da(a) == Catch::Approx(fd).epsilon(1e-5));
241 const auto bnds = curve->bounds();
242 REQUIRE(bnds.first <= 0.0);
243 REQUIRE(bnds.second >= 9.0);
261cp_region::RegionAtlas build_test_atlas() {
262 cp_region::RegionAtlas atlas;
265 auto axis = cp_region::AxisTransform::make(cp_region::AxisScale::LINEAR, 0.0, 10.0);
266 auto lo = std::make_unique<cp_region::ConstantCurve>(0.0, 10.0, 0.0);
267 auto hi = cp_region::PiecewiseChebyshevCurve::build(0.0, 10.0, 1, 4, cp_region::PiecewiseChebyshevCurve::ParamScale::LINEAR,
268 [](
double a) {
return 0.5 * a; });
269 atlas.add(cp_region::Region(axis, std::move(lo), std::move(hi)));
273 auto axis = cp_region::AxisTransform::make(cp_region::AxisScale::LINEAR, 0.0, 10.0);
274 auto lo = cp_region::PiecewiseChebyshevCurve::build(0.0, 10.0, 1, 4, cp_region::PiecewiseChebyshevCurve::ParamScale::LINEAR,
275 [](
double a) {
return 0.5 * a; });
276 auto hi = std::make_unique<cp_region::ConstantCurve>(0.0, 10.0, 10.0);
277 atlas.add(cp_region::Region(axis, std::move(lo), std::move(hi)));
284TEST_CASE(
"RegionAtlas dispatch picks the right region",
"[SVDComponents][Region][atlas_dispatch]") {
285 auto atlas = build_test_atlas();
286 REQUIRE(atlas.size() == 2);
289 REQUIRE(atlas.find_region(2.0, 0.3) == 0);
291 REQUIRE(atlas.find_region(2.0, 5.0) == 1);
293 REQUIRE(atlas.find_region(11.0, 5.0) == -1);
295 REQUIRE(atlas.find_region(2.0, -0.1) == -1);
298TEST_CASE(
"RegionAtlas AABB filter rejects strictly more than curve filter",
"[SVDComponents][Region][aabb_first]") {
299 auto atlas = build_test_atlas();
305 std::mt19937 rng(13);
306 std::uniform_real_distribution<double> ua(0.0, 10.0);
307 std::uniform_real_distribution<double> ub(0.0, 10.0);
310 for (
int k = 0; k < 10000; ++k) {
311 const double a = ua(rng);
312 const double b = ub(rng);
313 for (std::size_t i = 0; i < atlas.size(); ++i) {
314 if (atlas.region(i).aabb_contains(a, b)) {
316 if (atlas.region(i).curve_contains(a, b)) {
322 INFO(
"aabb_hits=" << aabb_hits <<
" curve_hits=" << curve_hits);
323 REQUIRE(aabb_hits > curve_hits);
325 REQUIRE(aabb_hits >= curve_hits);
328TEST_CASE(
"RegionAtlas::find_region rejects NaN inputs cleanly",
"[SVDComponents][Region][atlas_dispatch][nan]") {
329 auto atlas = build_test_atlas();
330 const double nan_v = std::numeric_limits<double>::quiet_NaN();
334 REQUIRE(atlas.find_region(nan_v, 1.0) == -1);
335 REQUIRE(atlas.find_region(1.0, nan_v) == -1);
336 REQUIRE(atlas.find_region(nan_v, nan_v) == -1);
339TEST_CASE(
"RegionAtlas handles 0- and 1-region cases",
"[SVDComponents][Region][atlas_dispatch]") {
340 cp_region::RegionAtlas empty;
341 REQUIRE(empty.size() == 0);
342 REQUIRE(empty.find_region(1.0, 1.0) == -1);
344 cp_region::RegionAtlas one;
346 auto axis = cp_region::AxisTransform::make(cp_region::AxisScale::LINEAR, 0.0, 10.0);
347 auto lo = std::make_unique<cp_region::ConstantCurve>(0.0, 10.0, 0.0);
348 auto hi = std::make_unique<cp_region::ConstantCurve>(0.0, 10.0, 10.0);
349 one.add(cp_region::Region(axis, std::move(lo), std::move(hi)));
351 REQUIRE(one.size() == 1);
352 REQUIRE(one.find_region(5.0, 5.0) == 0);
353 REQUIRE(one.find_region(11.0, 5.0) == -1);
354 REQUIRE(one.find_region(5.0, 11.0) == -1);
357TEST_CASE(
"Region moved-from instance fails aabb_contains cleanly",
"[SVDComponents][Region][move]") {
358 auto axis = cp_region::AxisTransform::make(cp_region::AxisScale::LINEAR, 0.0, 10.0);
359 auto lo = std::make_unique<cp_region::ConstantCurve>(0.0, 10.0, 0.0);
360 auto hi = std::make_unique<cp_region::ConstantCurve>(0.0, 10.0, 10.0);
361 cp_region::Region original(axis, std::move(lo), std::move(hi));
362 REQUIRE(original.aabb_contains(5.0, 5.0));
364 cp_region::Region moved = std::move(original);
365 REQUIRE(moved.aabb_contains(5.0, 5.0));
370 REQUIRE_FALSE(original.aabb_contains(5.0, 5.0));
371 REQUIRE_FALSE(original.aabb_contains(0.0, 0.0));
374TEST_CASE(
"PiecewiseChebyshevCurve::build rejects non-finite samples",
"[SVDComponents][Region][boundary_curve_cheb][nan]") {
375 using PCC = cp_region::PiecewiseChebyshevCurve;
376 REQUIRE_THROWS_AS(PCC::build(0.0, 10.0, 2, 6, PCC::ParamScale::LINEAR, [](
double ) {
return std::numeric_limits<double>::quiet_NaN(); }),
377 std::invalid_argument);
379 PCC::build(0.0, 10.0, 2, 6, PCC::ParamScale::LINEAR, [](
double a) {
return a < 5.0 ? 1.0 : std::numeric_limits<double>::infinity(); }),
380 std::invalid_argument);
383TEST_CASE(
"Region::to_normalized returns NaN on degenerate span",
"[SVDComponents][Region][to_normalized][degenerate]") {
385 auto axis = cp_region::AxisTransform::make(cp_region::AxisScale::LINEAR, 0.0, 10.0);
386 auto lo = std::make_unique<cp_region::ConstantCurve>(0.0, 10.0, 5.0);
387 auto hi = std::make_unique<cp_region::ConstantCurve>(0.0, 10.0, 5.0);
388 cp_region::Region pinched(axis, std::move(lo), std::move(hi));
389 const auto [xi, eta] = pinched.to_normalized(3.0, 5.0);
390 REQUIRE(std::isfinite(xi));
391 REQUIRE(std::isnan(eta));
394TEST_CASE(
"PiecewiseChebyshevCurve tight bounds match interior extrema",
"[SVDComponents][Region][boundary_curve_cheb][bounds]") {
398 using PCC = cp_region::PiecewiseChebyshevCurve;
399 auto curve = PCC::build(0.0, 1.0, 2, 12, PCC::ParamScale::LINEAR, [](
double a) {
return std::sin(2.0 * kPi * a); });
400 const auto bnds = curve->bounds();
403 REQUIRE(bnds.first == Catch::Approx(-1.0).margin(1e-6));
404 REQUIRE(bnds.second == Catch::Approx(1.0).margin(1e-6));
407TEST_CASE(
"Region (a, b) <-> (xi, eta) round-trip",
"[SVDComponents][Region][round_trip]") {
408 auto atlas = build_test_atlas();
409 std::mt19937 rng(17);
410 std::uniform_real_distribution<double> u(0.0, 1.0);
411 int round_tripped = 0;
412 for (
int k = 0; k < 200; ++k) {
413 const double xi = u(rng);
414 const double eta = u(rng);
415 for (std::size_t i = 0; i < atlas.size(); ++i) {
416 const auto ab = atlas.region(i).from_normalized(xi, eta);
417 const auto xe = atlas.region(i).to_normalized(ab.first, ab.second);
418 REQUIRE(xe.first == Catch::Approx(xi).epsilon(1e-12));
419 REQUIRE(xe.second == Catch::Approx(eta).epsilon(1e-12));
423 REQUIRE(round_tripped > 0);
443cp_svd::SVDDecomposition build_test_decomposition(
cp_svd::SlopeSource slope_source, std::int32_t rank) {
445 std::vector<double> xg(N), yg(N), M(N * N);
446 for (
int i = 0; i < N; ++i) {
447 xg[i] =
static_cast<double>(i) / (N - 1);
449 for (
int j = 0; j < N; ++j) {
450 yg[j] =
static_cast<double>(j) / (N - 1);
452 for (
int i = 0; i < N; ++i) {
453 for (
int j = 0; j < N; ++j) {
454 M[i * N + j] = std::sin(kPi * xg[i]) * (1.0 + 0.3 * std::sin(kPi * yg[j]));
457 cp_svd::SVDBuildOptions opts;
459 opts.out_transform = cp_svd::OutputTransform::IDENTITY;
460 opts.slope_source = slope_source;
464double test_function_truth(
double x,
double y) {
465 return std::sin(kPi * x) * (1.0 + 0.3 * std::sin(kPi * y));
470TEST_CASE(
"SVD round-trip on a smooth analytic 2D function",
"[SVDComponents][SVD][roundtrip]") {
471 auto d = build_test_decomposition(cp_svd::SlopeSource::NATURAL_CUBIC_SPLINE, 4);
472 cp_svd::SVDEvaluator ev(d);
473 std::mt19937 rng(19);
474 std::uniform_real_distribution<double> u(0.05, 0.95);
475 double max_rel = 0.0;
476 for (
int k = 0; k < 2000; ++k) {
477 const double x = u(rng);
478 const double y = u(rng);
479 const double truth = test_function_truth(x, y);
480 const double pred = ev.eval(x, y);
481 const double rel = std::abs(pred - truth) / std::max(std::abs(truth), 1e-12);
486 INFO(
"max_rel = " << max_rel);
487 REQUIRE(max_rel < 1e-4);
490TEST_CASE(
"SVDEvaluator shared_ptr constructor owns the decomposition",
"[SVDComponents][SVD][shared_ptr_ownership]") {
491 auto d_ptr = std::make_shared<cp_svd::SVDDecomposition>(build_test_decomposition(cp_svd::SlopeSource::NATURAL_CUBIC_SPLINE, 4));
492 cp_svd::SVDEvaluator ev{d_ptr};
496 const double truth = test_function_truth(0.3, 0.7);
497 const double pred = ev.eval(0.3, 0.7);
498 REQUIRE(std::abs(pred - truth) / std::max(std::abs(truth), 1e-12) < 1e-4);
501TEST_CASE(
"SVDEvaluator EXP transform stays finite on extrapolation",
"[SVDComponents][SVD][exp_overflow]") {
509 std::vector<double> xg(N), yg(N), M(N * N);
510 for (
int i = 0; i < N; ++i) {
511 xg[i] =
static_cast<double>(i) / (N - 1);
513 for (
int j = 0; j < N; ++j) {
514 yg[j] =
static_cast<double>(j) / (N - 1);
516 for (
int i = 0; i < N; ++i) {
517 for (
int j = 0; j < N; ++j) {
519 M[i * N + j] = std::sin(kPi * xg[i]) * std::cos(kPi * yg[j]);
522 cp_svd::SVDBuildOptions opts;
524 opts.out_transform = cp_svd::OutputTransform::EXP;
526 cp_svd::SVDEvaluator ev(d);
528 REQUIRE(std::isfinite(ev.eval(0.001, 0.001)));
529 REQUIRE(std::isfinite(ev.eval(0.999, 0.999)));
532 REQUIRE(std::isfinite(ev.eval(0.0, 0.0)));
533 REQUIRE(std::isfinite(ev.eval(1.0, 1.0)));
536TEST_CASE(
"SVD natural-cubic-spline slopes outperform Hermite-FD",
"[SVDComponents][SVD][slope_source]") {
541 auto d_fd = build_test_decomposition(cp_svd::SlopeSource::HERMITE_FD, 4);
542 auto d_spline = build_test_decomposition(cp_svd::SlopeSource::NATURAL_CUBIC_SPLINE, 4);
543 cp_svd::SVDEvaluator ev_fd(d_fd);
544 cp_svd::SVDEvaluator ev_spline(d_spline);
545 std::mt19937 rng(21);
546 std::uniform_real_distribution<double> u(0.05, 0.95);
547 double max_fd = 0.0, max_spline = 0.0;
548 for (
int k = 0; k < 2000; ++k) {
549 const double x = u(rng);
550 const double y = u(rng);
551 const double truth = test_function_truth(x, y);
552 const double rel_fd = std::abs(ev_fd.eval(x, y) - truth) / std::max(std::abs(truth), 1e-12);
553 const double rel_spline = std::abs(ev_spline.eval(x, y) - truth) / std::max(std::abs(truth), 1e-12);
554 if (rel_fd > max_fd) {
557 if (rel_spline > max_spline) {
558 max_spline = rel_spline;
561 INFO(
"max_fd=" << max_fd <<
" max_spline=" << max_spline);
562 REQUIRE(max_spline <= max_fd);
565TEST_CASE(
"SVD EXP transform recovers values from log-space build",
"[SVDComponents][SVD][roundtrip]") {
569 std::vector<double> xg(N), yg(N), M(N * N);
570 for (
int i = 0; i < N; ++i) {
571 xg[i] = 0.1 + 4.0 * i / (N - 1);
573 for (
int j = 0; j < N; ++j) {
574 yg[j] = 0.1 + 4.0 * j / (N - 1);
576 for (
int i = 0; i < N; ++i) {
577 for (
int j = 0; j < N; ++j) {
579 M[i * N + j] = std::log(xg[i] * xg[i] + yg[j] * yg[j] + 1.0);
582 cp_svd::SVDBuildOptions opts;
584 opts.out_transform = cp_svd::OutputTransform::EXP;
586 cp_svd::SVDEvaluator ev(d);
587 std::mt19937 rng(23);
588 std::uniform_real_distribution<double> u(0.2, 3.9);
589 double max_rel = 0.0;
590 for (
int k = 0; k < 1000; ++k) {
591 const double x = u(rng);
592 const double y = u(rng);
593 const double truth_log = std::log(x * x + y * y + 1.0);
594 const double truth = std::exp(truth_log);
595 const double pred = ev.eval(x, y);
596 const double rel = std::abs(pred - truth) / truth;
601 REQUIRE(max_rel < 1e-2);
608TEST_CASE(
"SVDEvaluator::eval timing",
"[SVDComponents][!benchmark]") {
609 auto d = build_test_decomposition(cp_svd::SlopeSource::NATURAL_CUBIC_SPLINE, 20);
610 cp_svd::SVDEvaluator ev(d);
611 std::mt19937 rng(101);
612 std::uniform_real_distribution<double> u(0.05, 0.95);
613 std::vector<std::pair<double, double>> probes;
614 probes.reserve(1000);
615 for (
int i = 0; i < 1000; ++i) {
616 probes.emplace_back(u(rng), u(rng));
619 BENCHMARK(
"eval rank-20") {
621 for (
const auto& p : probes) {
622 sink += ev.eval(p.first, p.second);
628TEST_CASE(
"RegionAtlas::find_region timing",
"[SVDComponents][!benchmark]") {
648 auto atlas = build_test_atlas();
649 constexpr int N_PROBES = 1000;
650 std::mt19937 rng(103);
651 std::uniform_real_distribution<double> u01(0.0, 1.0);
653 std::vector<std::pair<double, double>> miss_probes;
654 std::vector<std::pair<double, double>> first_probes;
655 std::vector<std::pair<double, double>> last_probes;
656 miss_probes.reserve(N_PROBES);
657 first_probes.reserve(N_PROBES);
658 last_probes.reserve(N_PROBES);
659 while (
static_cast<int>(miss_probes.size()) < N_PROBES) {
661 miss_probes.emplace_back(11.0 + 9.0 * u01(rng), 10.0 * u01(rng));
663 while (
static_cast<int>(first_probes.size()) < N_PROBES) {
666 const double a = 10.0 * u01(rng);
667 const double b = 0.5 * a * u01(rng);
668 first_probes.emplace_back(a, b);
670 while (
static_cast<int>(last_probes.size()) < N_PROBES) {
674 const double a = 10.0 * u01(rng);
675 const double b_lo = 0.5 * a;
679 const double b = b_lo + (5.0 - b_lo) * u01(rng);
680 last_probes.emplace_back(a, b);
683 BENCHMARK(
"find_region — atlas-miss (AABB filter only)") {
685 for (
const auto& p : miss_probes) {
686 sink += atlas.find_region(p.first, p.second);
691 BENCHMARK(
"find_region — first-region hit (1 AABB + 1 curve)") {
693 for (
const auto& p : first_probes) {
694 sink += atlas.find_region(p.first, p.second);
699 BENCHMARK(
"find_region — last-region hit (2 AABBs + 2 curves)") {
701 for (
const auto& p : last_probes) {
702 sink += atlas.find_region(p.first, p.second);