File indexing completed on 2025-08-05 08:10:22
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Definitions/Algebra.hpp"
0010 #include "Acts/Definitions/Units.hpp"
0011 #include "Acts/Surfaces/BoundaryCheck.hpp"
0012 #include "Acts/Tests/CommonHelpers/BenchmarkTools.hpp"
0013 #include "Acts/Utilities/BoundingBox.hpp"
0014 #include "Acts/Utilities/Frustum.hpp"
0015 #include "Acts/Utilities/Ray.hpp"
0016
0017 #include <algorithm>
0018 #include <chrono>
0019 #include <functional>
0020 #include <iostream>
0021 #include <map>
0022 #include <random>
0023 #include <vector>
0024
0025 using namespace Acts;
0026
0027 struct O {};
0028 using Box = Acts::AxisAlignedBoundingBox<O, float, 3>;
0029 using VertexType = Box::VertexType;
0030 using vertex_array_type = Box::vertex_array_type;
0031 using value_type = Box::value_type;
0032 using Frustum3 = Frustum<float, 3, 4>;
0033 using Ray3 = Ray<float, 3>;
0034
0035 int main(int , char** ) {
0036 std::size_t n = 1000;
0037
0038 std::mt19937 rng(42);
0039 std::uniform_real_distribution<float> dir(0, 1);
0040 std::uniform_real_distribution<float> loc(-10, 10);
0041 std::uniform_real_distribution<float> ang(M_PI / 10., M_PI / 4.);
0042
0043 Box testBox{nullptr, {0, 0, 0}, Box::Size{{1, 2, 3}}};
0044
0045 std::cout << "\n==== RAY ====\n" << std::endl;
0046
0047 std::map<std::string, bool (*)(const Box&, const Ray3&)> rayVariants;
0048
0049 rayVariants["Nominal"] = [](const auto& box, const auto& ray) -> bool {
0050 return box.intersect(ray);
0051 };
0052
0053 rayVariants["Incl. div., unroll"] = [](const Box& box,
0054 const Ray<float, 3>& ray) {
0055 const VertexType& origin = ray.origin();
0056
0057 const vertex_array_type& d = ray.dir();
0058
0059 double tmin = -INFINITY, tmax = INFINITY;
0060 if (d.x() != 0.0) {
0061 double tx1 = (box.min().x() - origin.x()) / d.x();
0062 double tx2 = (box.max().x() - origin.x()) / d.x();
0063
0064 tmin = std::max(tmin, std::min(tx1, tx2));
0065 tmax = std::min(tmax, std::max(tx1, tx2));
0066 }
0067
0068 if (d.y() != 0.0) {
0069 double ty1 = (box.min().y() - origin.y()) / d.y();
0070 double ty2 = (box.max().y() - origin.y()) / d.y();
0071
0072 tmin = std::max(tmin, std::min(ty1, ty2));
0073 tmax = std::min(tmax, std::max(ty1, ty2));
0074 }
0075
0076 if (d.z() != 0.0) {
0077 double tz1 = (box.min().z() - origin.z()) / d.z();
0078 double tz2 = (box.max().z() - origin.z()) / d.z();
0079
0080 tmin = std::max(tmin, std::min(tz1, tz2));
0081 tmax = std::min(tmax, std::max(tz1, tz2));
0082 }
0083
0084 return tmax > tmin && tmax > 0.0;
0085 };
0086
0087 rayVariants["Incl. div., loop"] = [](const Box& box,
0088 const Ray<float, 3>& ray) {
0089 const VertexType& origin = ray.origin();
0090
0091 const vertex_array_type& d = ray.dir();
0092
0093 double tmin = -INFINITY, tmax = INFINITY;
0094
0095 for (std::size_t i = 0; i < 3; i++) {
0096 if (d[i] == 0.0) {
0097 continue;
0098 }
0099 double t1 = (box.min()[i] - origin[i]) / d[i];
0100 double t2 = (box.max()[i] - origin[i]) / d[i];
0101 tmin = std::max(tmin, std::min(t1, t2));
0102 tmax = std::min(tmax, std::max(t1, t2));
0103 }
0104
0105 return tmax > tmin && tmax > 0.0;
0106 };
0107
0108 rayVariants["Incl. div., min/max alt., unroll"] =
0109 [](const Box& box, const Ray<float, 3>& ray) {
0110 const VertexType& origin = ray.origin();
0111 const vertex_array_type& d = ray.dir();
0112
0113 double tx1 = (box.min().x() - origin.x()) / d.x();
0114 double tx2 = (box.max().x() - origin.x()) / d.x();
0115 double tmin = std::min(tx1, tx2);
0116 double tmax = std::max(tx1, tx2);
0117
0118 double ty1 = (box.min().y() - origin.y()) / d.y();
0119 double ty2 = (box.max().y() - origin.y()) / d.y();
0120 tmin = std::max(tmin, std::min(ty1, ty2));
0121 tmax = std::min(tmax, std::max(ty1, ty2));
0122
0123 double tz1 = (box.min().z() - origin.z()) / d.z();
0124 double tz2 = (box.max().z() - origin.z()) / d.z();
0125 tmin = std::max(tmin, std::min(tz1, tz2));
0126 tmax = std::min(tmax, std::max(tz1, tz2));
0127
0128 return tmax > tmin && tmax > 0.0;
0129 };
0130
0131 rayVariants["No div., min/max alt, unroll"] = [](const Box& box,
0132 const Ray<float, 3>& ray) {
0133 const VertexType& origin = ray.origin();
0134 const vertex_array_type& id = ray.idir();
0135
0136 double tx1 = (box.min().x() - origin.x()) * id.x();
0137 double tx2 = (box.max().x() - origin.x()) * id.x();
0138 double tmin = std::min(tx1, tx2);
0139 double tmax = std::max(tx1, tx2);
0140
0141 double ty1 = (box.min().y() - origin.y()) * id.y();
0142 double ty2 = (box.max().y() - origin.y()) * id.y();
0143 tmin = std::max(tmin, std::min(ty1, ty2));
0144 tmax = std::min(tmax, std::max(ty1, ty2));
0145
0146 double tz1 = (box.min().z() - origin.z()) * id.z();
0147 double tz2 = (box.max().z() - origin.z()) * id.z();
0148 tmin = std::max(tmin, std::min(tz1, tz2));
0149 tmax = std::min(tmax, std::max(tz1, tz2));
0150
0151 return tmax > tmin && tmax > 0.0;
0152 };
0153
0154 rayVariants["No div., min/max orig, loop"] = [](const Box& box,
0155 const Ray<float, 3>& ray) {
0156 const VertexType& origin = ray.origin();
0157 const vertex_array_type& id = ray.idir();
0158 double tmin = -INFINITY, tmax = INFINITY;
0159
0160 for (std::size_t i = 0; i < 3; i++) {
0161 double t1 = (box.min()[i] - origin[i]) * id[i];
0162 double t2 = (box.max()[i] - origin[i]) * id[i];
0163 tmin = std::max(tmin, std::min(t1, t2));
0164 tmax = std::min(tmax, std::max(t1, t2));
0165 }
0166
0167 return tmax > tmin && tmax > 0.0;
0168 };
0169
0170 using Vector3F = Eigen::Matrix<float, 3, 1>;
0171
0172 std::vector<Ray3> rays{n, Ray3{Vector3F{0, 0, 0}, Vector3F{1, 0, 0}}};
0173 std::generate(rays.begin(), rays.end(), [&]() {
0174 const Vector3F d{dir(rng), dir(rng), dir(rng)};
0175 const Vector3F l{loc(rng), loc(rng), loc(rng)};
0176 return Ray3{l, d.normalized()};
0177 });
0178
0179 std::cout << "Make sure ray implementations are identical" << std::endl;
0180 for (const auto& ray : rays) {
0181 std::vector<std::pair<std::string, bool>> results;
0182
0183 std::transform(rayVariants.begin(), rayVariants.end(),
0184 std::back_inserter(results),
0185 [&](const auto& p) -> decltype(results)::value_type {
0186 const auto& [name, func] = p;
0187 return {name, func(testBox, ray)};
0188 });
0189
0190 bool all = std::all_of(results.begin(), results.end(),
0191 [](const auto& r) { return r.second; });
0192 bool none = std::none_of(results.begin(), results.end(),
0193 [](const auto& r) { return r.second; });
0194
0195 if (!all && !none) {
0196 std::cerr << "Discrepancy: " << std::endl;
0197 for (const auto& [name, result] : results) {
0198 std::cerr << " - " << name << ": " << result << std::endl;
0199 }
0200
0201 testBox.toStream(std::cerr);
0202 std::cerr << std::endl;
0203 std::cerr << "Ray: [" << ray.origin().transpose() << "], ["
0204 << ray.dir().transpose() << "]" << std::endl;
0205 return -1;
0206 }
0207 }
0208 std::cout << "Seems ok" << std::endl;
0209
0210 std::cout << "Run benchmarks: " << std::endl;
0211 for (const auto& p : rayVariants) {
0212
0213 std::cout << "- Benchmarking variant: '" << p.first << "'" << std::endl;
0214 auto bench_result = Acts::Test::microBenchmark(
0215 [&](const auto& ray) { return p.second(testBox, ray); }, rays);
0216 std::cout << " " << bench_result << std::endl;
0217 }
0218
0219 std::cout << "\n==== FRUSTUM ====\n" << std::endl;
0220
0221 std::map<std::string, bool (*)(const Box&, const Frustum3&)> frustumVariants;
0222
0223 frustumVariants["Nominal"] = [](const auto& box,
0224 const auto& frustum) -> bool {
0225 return box.intersect(frustum);
0226 };
0227
0228 frustumVariants["Manual constexpr loop unroll, early ret."] =
0229 [](const Box& box, const Frustum3& fr) {
0230 constexpr std::size_t sides = 4;
0231
0232
0233 const auto& normals = fr.normals();
0234 const vertex_array_type fr_vmin = box.min() - fr.origin();
0235 const vertex_array_type fr_vmax = box.max() - fr.origin();
0236
0237 auto calc = [&](const auto& normal) {
0238 return (normal.array() < 0).template cast<value_type>() * fr_vmin +
0239 (normal.array() >= 0).template cast<value_type>() * fr_vmax;
0240 };
0241
0242 VertexType p_vtx;
0243
0244 p_vtx = calc(normals[0]);
0245 if (p_vtx.dot(normals[0]) < 0) {
0246 return false;
0247 }
0248
0249 p_vtx = calc(normals[1]);
0250 if (p_vtx.dot(normals[1]) < 0) {
0251 return false;
0252 }
0253
0254 p_vtx = calc(normals[2]);
0255 if (p_vtx.dot(normals[2]) < 0) {
0256 return false;
0257 }
0258
0259 if constexpr (sides > 2) {
0260 p_vtx = calc(normals[3]);
0261 if (p_vtx.dot(normals[3]) < 0) {
0262 return false;
0263 }
0264 }
0265
0266 if constexpr (sides > 3) {
0267 p_vtx = calc(normals[4]);
0268 if (p_vtx.dot(normals[4]) < 0) {
0269 return false;
0270 }
0271 }
0272
0273 if constexpr (sides > 4) {
0274 for (std::size_t i = 5; i <= fr.sides; i++) {
0275 const VertexType& normal = normals[i];
0276
0277 p_vtx = calc(normal);
0278 if (p_vtx.dot(normal) < 0) {
0279 return false;
0280 }
0281 }
0282 }
0283
0284 return true;
0285 };
0286
0287 frustumVariants["Nominal, no early ret."] = [](const Box& box,
0288 const Frustum3& fr) {
0289 const auto& normals = fr.normals();
0290 const vertex_array_type fr_vmin = box.min() - fr.origin();
0291 const vertex_array_type fr_vmax = box.max() - fr.origin();
0292
0293 VertexType p_vtx;
0294 bool result = true;
0295 for (std::size_t i = 0; i < fr.sides + 1; i++) {
0296 const VertexType& normal = normals[i];
0297
0298 p_vtx = (normal.array() < 0).template cast<value_type>() * fr_vmin +
0299 (normal.array() >= 0).template cast<value_type>() * fr_vmax;
0300
0301 result = result && (p_vtx.dot(normal) >= 0);
0302 }
0303 return result;
0304 };
0305
0306 frustumVariants["Manual constexpr unroll, early ret."] =
0307 [](const Box& box, const Frustum3& fr) {
0308 constexpr std::size_t sides = 4;
0309
0310
0311 const auto& normals = fr.normals();
0312 const vertex_array_type fr_vmin = box.min() - fr.origin();
0313 const vertex_array_type fr_vmax = box.max() - fr.origin();
0314
0315 auto calc = [&](const auto& normal) {
0316 return (normal.array() < 0).template cast<value_type>() * fr_vmin +
0317 (normal.array() >= 0).template cast<value_type>() * fr_vmax;
0318 };
0319
0320 VertexType p_vtx;
0321 bool result = true;
0322
0323 p_vtx = calc(normals[0]);
0324 result = result && (p_vtx.dot(normals[0]) >= 0);
0325
0326 p_vtx = calc(normals[1]);
0327 result = result && (p_vtx.dot(normals[1]) >= 0);
0328
0329 p_vtx = calc(normals[2]);
0330 result = result && (p_vtx.dot(normals[2]) >= 0);
0331
0332 if constexpr (sides > 2) {
0333 p_vtx = calc(normals[3]);
0334 result = result && (p_vtx.dot(normals[3]) >= 0);
0335 }
0336
0337 if constexpr (sides > 3) {
0338 p_vtx = calc(normals[4]);
0339 result = result && (p_vtx.dot(normals[4]) >= 0);
0340 }
0341
0342 if constexpr (sides > 4) {
0343 for (std::size_t i = 5; i <= fr.sides; i++) {
0344 const VertexType& normal = normals[i];
0345
0346 p_vtx = calc(normal);
0347 result = result && (p_vtx.dot(normal) >= 0);
0348 }
0349 }
0350
0351 return result;
0352 };
0353
0354 std::vector<Frustum3> frustums{n, Frustum3{{0, 0, 0}, {1, 0, 0}, M_PI / 2.}};
0355 std::generate(frustums.begin(), frustums.end(), [&]() {
0356 const Vector3F d{dir(rng), dir(rng), dir(rng)};
0357 const Vector3F l{loc(rng), loc(rng), loc(rng)};
0358 return Frustum3{l, d.normalized(), ang(rng)};
0359 });
0360
0361 std::cout << "Make sure frustum implementations are identical" << std::endl;
0362 for (const auto& fr : frustums) {
0363 std::vector<std::pair<std::string, bool>> results;
0364
0365 std::transform(frustumVariants.begin(), frustumVariants.end(),
0366 std::back_inserter(results),
0367 [&](const auto& p) -> decltype(results)::value_type {
0368 const auto& [name, func] = p;
0369 return {name, func(testBox, fr)};
0370 });
0371
0372 bool all = std::all_of(results.begin(), results.end(),
0373 [](const auto& r) { return r.second; });
0374 bool none = std::none_of(results.begin(), results.end(),
0375 [](const auto& r) { return r.second; });
0376
0377 if (!all && !none) {
0378 std::cerr << "Discrepancy: " << std::endl;
0379 for (const auto& [name, result] : results) {
0380 std::cerr << " - " << name << ": " << result << std::endl;
0381 }
0382
0383 testBox.toStream(std::cerr);
0384 std::cerr << std::endl;
0385 std::cerr << "Frustum: [" << fr.origin().transpose() << "], ["
0386 << fr.dir().transpose() << "]" << std::endl;
0387 return -1;
0388 }
0389 }
0390 std::cout << "Seems ok" << std::endl;
0391
0392 std::size_t iters_per_run = 1000;
0393
0394 std::vector<std::pair<std::string, Frustum3>> testFrusts = {
0395 {"away", Frustum3{{0, 0, -10}, {0, 0, -1}, M_PI / 4.}},
0396 {"towards", Frustum3{{0, 0, -10}, {0, 0, 1}, M_PI / 4.}},
0397 {"left", Frustum3{{0, 0, -10}, {0, 1, 0}, M_PI / 4.}},
0398 {"right", Frustum3{{0, 0, -10}, {0, -1, 0}, M_PI / 4.}},
0399 {"up", Frustum3{{0, 0, -10}, {1, 0, 0}, M_PI / 4.}},
0400 {"down", Frustum3{{0, 0, -10}, {-1, 0, 0}, M_PI / 4.}},
0401 };
0402
0403 std::cout << "Run benchmarks: " << std::endl;
0404
0405 for (const auto& fr_pair : testFrusts) {
0406 std::cout << "Frustum '" << fr_pair.first << "'" << std::endl;
0407
0408 for (const auto& p : frustumVariants) {
0409
0410 std::cout << "- Benchmarking variant: '" << p.first << "'" << std::endl;
0411 auto bench_result = Acts::Test::microBenchmark(
0412 [&]() { return p.second(testBox, fr_pair.second); }, iters_per_run);
0413 std::cout << " " << bench_result << std::endl;
0414 }
0415
0416 std::cout << std::endl;
0417 }
0418 return 0;
0419 }