File indexing completed on 2026-10-05 08:10:43
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Surfaces/SurfaceArray.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Geometry/GeometryContext.hpp"
0013 #include "Acts/Geometry/Polyhedron.hpp"
0014 #include "Acts/Surfaces/CylinderBounds.hpp"
0015 #include "Acts/Surfaces/Surface.hpp"
0016 #include "Acts/Utilities/AlgebraHelpers.hpp"
0017 #include "Acts/Utilities/Axis.hpp"
0018 #include "Acts/Utilities/Helpers.hpp"
0019 #include "Acts/Utilities/IAxis.hpp"
0020 #include "Acts/Utilities/Ranges.hpp"
0021 #include "Acts/Utilities/ThrowAssert.hpp"
0022 #include "Acts/Utilities/detail/MultiAxisHelper.hpp"
0023 #include "Acts/Utilities/detail/OstreamStateGuard.hpp"
0024
0025 #include <algorithm>
0026 #include <cmath>
0027 #include <iomanip>
0028 #include <limits>
0029 #include <map>
0030 #include <ranges>
0031 #include <set>
0032 #include <stdexcept>
0033 #include <unordered_set>
0034 #include <utility>
0035
0036 namespace Acts {
0037
0038
0039 struct SurfaceArray::ISurfaceGridLookup {
0040 virtual ~ISurfaceGridLookup() = default;
0041
0042
0043
0044
0045 virtual void fill(const GeometryContext& gctx,
0046 std::span<const Surface* const> surfaces) = 0;
0047
0048
0049
0050
0051 virtual std::span<const Surface* const> at(std::size_t bin) const = 0;
0052
0053
0054
0055
0056
0057
0058 virtual std::span<const Surface* const> at(
0059 const GeometryContext& gctx, const Vector3& position,
0060 const Vector3& direction) const = 0;
0061
0062
0063
0064
0065
0066
0067
0068 virtual std::span<const Surface* const> neighbors(
0069 std::array<std::size_t, 2> gridIndices,
0070 std::array<std::uint8_t, 2> neighborDistance) const = 0;
0071
0072
0073
0074
0075
0076
0077 virtual std::span<const Surface* const> neighbors(
0078 const GeometryContext& gctx, const Vector3& position,
0079 const Vector3& direction) const = 0;
0080
0081
0082
0083 virtual std::size_t size() const = 0;
0084
0085
0086
0087
0088 virtual Vector3 getBinCenter(std::size_t bin) const = 0;
0089
0090
0091
0092
0093 virtual std::vector<const IAxis*> getAxes() const = 0;
0094
0095
0096
0097 virtual const Surface* surfaceRepresentation() const = 0;
0098
0099
0100
0101
0102
0103
0104 virtual bool isValidBin(std::size_t bin) const = 0;
0105
0106
0107
0108
0109 virtual std::vector<AxisDirection> binningValues() const { return {}; }
0110
0111
0112
0113
0114 virtual std::array<std::size_t, 2> numLocalBins() const = 0;
0115
0116
0117
0118 virtual SurfaceArray::NeighborWindow neighborWindow() const = 0;
0119 };
0120
0121 namespace {
0122
0123 struct SingleElementLookupImpl final : SurfaceArray::ISurfaceGridLookup {
0124 explicit SingleElementLookupImpl(const Surface* element)
0125 : m_element({element}) {}
0126
0127 std::span<const Surface* const> at(std::size_t bin) const override {
0128 if (bin != 0) {
0129 throw std::out_of_range(
0130 "SingleElementLookupImpl only contains one bin with index 0");
0131 }
0132 return m_element;
0133 }
0134
0135 std::span<const Surface* const> at(
0136 const GeometryContext& , const Vector3& ,
0137 const Vector3& ) const override {
0138 return m_element;
0139 }
0140
0141 std::span<const Surface* const> neighbors(
0142 std::array<std::size_t, 2> gridIndices,
0143 std::array<std::uint8_t, 2> neighborDistance) const override {
0144 if (gridIndices != std::array<std::size_t, 2>{0, 0} ||
0145 neighborDistance != std::array<std::uint8_t, 2>{0, 0}) {
0146 throw std::out_of_range(
0147 "SingleElementLookupImpl only contains one bin with zero neighbor "
0148 "distance");
0149 }
0150 return m_element;
0151 }
0152
0153 std::span<const Surface* const> neighbors(
0154 const GeometryContext& , const Vector3& ,
0155 const Vector3& ) const override {
0156 return m_element;
0157 }
0158
0159 std::size_t size() const override { return 1; }
0160
0161 Vector3 getBinCenter(std::size_t ) const override {
0162 return Vector3(0, 0, 0);
0163 }
0164
0165 std::vector<const IAxis*> getAxes() const override { return {}; }
0166
0167 const Surface* surfaceRepresentation() const override { return nullptr; }
0168
0169 void fill(const GeometryContext& ,
0170 std::span<const Surface* const> ) override {
0171
0172 }
0173
0174 bool isValidBin(std::size_t bin) const override { return bin == 0; }
0175
0176 std::array<std::size_t, 2> numLocalBins() const override { return {1, 1}; }
0177
0178 SurfaceArray::NeighborWindow neighborWindow() const override {
0179 return {{0, 0}, {0, 0}};
0180 }
0181
0182 private:
0183 std::vector<const Surface*> m_element;
0184 };
0185
0186 template <class Axis1, class Axis2>
0187 struct SurfaceGridLookupImpl final : SurfaceArray::ISurfaceGridLookup {
0188 SurfaceGridLookupImpl(std::shared_ptr<RegularSurface> representative,
0189 double tolerance, std::tuple<Axis1, Axis2> axes,
0190 std::vector<AxisDirection> binValues = {},
0191 SurfaceArray::NeighborWindow neighborWindow = {},
0192 std::uint8_t overfill = 0)
0193 : m_representative(std::move(representative)),
0194 m_tolerance(tolerance),
0195 m_axes(std::move(axes)),
0196 m_binValues(std::move(binValues)),
0197 m_neighborWindow(neighborWindow),
0198 m_overfill(overfill) {
0199 if (m_neighborWindow.min[0] > m_neighborWindow.max[0] ||
0200 m_neighborWindow.min[1] > m_neighborWindow.max[1]) {
0201 throw std::invalid_argument("neighbor window floor exceeds its bound");
0202 }
0203
0204 m_numLocalBins = {std::get<0>(m_axes).getNBins(),
0205 std::get<1>(m_axes).getNBins()};
0206 m_numGridBins = (m_numLocalBins[0] + 2) * (m_numLocalBins[1] + 2);
0207 m_packStride1 = static_cast<std::size_t>(m_neighborWindow.max[1]) + 1;
0208 m_packStridePerBin =
0209 (static_cast<std::size_t>(m_neighborWindow.max[0]) + 1) * m_packStride1;
0210 m_windowIsFixed = m_neighborWindow.min == m_neighborWindow.max;
0211 if (const auto* cylinder =
0212 dynamic_cast<const CylinderBounds*>(&m_representative->bounds());
0213 cylinder != nullptr) {
0214 m_gridScale = cylinder->get(CylinderBounds::eR);
0215 }
0216 }
0217
0218 void fill(const GeometryContext& gctx,
0219 std::span<const Surface* const> surfaces) override {
0220
0221 FillingGrid fillingGrid(m_numGridBins);
0222
0223 for (const Surface* surface : surfaces) {
0224 fillSurfaceFootprint(gctx, *surface, fillingGrid);
0225 }
0226
0227 for (std::vector<const Surface*>& binSurfaces : fillingGrid) {
0228 std::ranges::sort(binSurfaces);
0229 const auto last = std::ranges::unique(binSurfaces);
0230 binSurfaces.erase(last.begin(), last.end());
0231 }
0232
0233 checkGrid(surfaces, fillingGrid);
0234
0235 populateNeighborCache(fillingGrid);
0236 }
0237
0238 std::span<const Surface* const> at(std::size_t globalBin) const override {
0239 if (globalBin >= m_numGridBins) {
0240 throw std::out_of_range("global bin is outside the grid");
0241 }
0242
0243 return surfacePack(globalBin * m_packStridePerBin);
0244 }
0245
0246 std::span<const Surface* const> at(const GeometryContext& gctx,
0247 const Vector3& position,
0248 const Vector3& direction) const override {
0249 const std::optional<GridIndex> localBins = findLocalBin2D(
0250 gctx, position, direction, std::numeric_limits<double>::infinity());
0251 if (!localBins.has_value()) {
0252 return {};
0253 }
0254 return surfacePack(neighborPackIndex(*localBins, {0, 0}));
0255 }
0256
0257 std::span<const Surface* const> neighbors(
0258 std::array<std::size_t, 2> gridIndices,
0259 std::array<std::uint8_t, 2> neighborDistance) const override {
0260
0261 if (neighborDistance[0] > m_neighborWindow.max[0] ||
0262 neighborDistance[1] > m_neighborWindow.max[1]) {
0263 throw std::out_of_range(
0264 "neighbor distance exceeds the neighbor window bound");
0265 }
0266
0267 if (gridIndices[0] > m_numLocalBins[0] + 1 ||
0268 gridIndices[1] > m_numLocalBins[1] + 1) {
0269 throw std::out_of_range("local bin is outside the grid");
0270 }
0271 return surfacePack(neighborPackIndex(gridIndices, neighborDistance));
0272 }
0273
0274 std::span<const Surface* const> neighbors(
0275 const GeometryContext& gctx, const Vector3& position,
0276 const Vector3& direction) const override {
0277 const std::optional<Crossing> crossing = findCrossing(
0278 gctx, position, direction, std::numeric_limits<double>::infinity());
0279 if (!crossing.has_value()) {
0280 return {};
0281 }
0282
0283 const GridPoint gridLocal = surfaceToGridLocal(crossing->local);
0284 const GridIndex localBins = localBinsFromPosition2D(gridLocal);
0285
0286 const GridDistance neighborDistance = crossingNeighborDistance(
0287 gctx, *crossing, direction, gridLocal, localBins);
0288
0289 return surfacePack(neighborPackIndex(localBins, neighborDistance));
0290 }
0291
0292 std::size_t size() const override { return m_numGridBins; }
0293
0294 std::vector<AxisDirection> binningValues() const override {
0295 return m_binValues;
0296 }
0297
0298 Vector3 getBinCenter(std::size_t bin) const override {
0299 const GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0300 const GridPoint gridLocal = binCenter(localBinsFromGlobalBin2D(bin));
0301 const Vector2 surfaceLocal = gridToSurfaceLocal(gridLocal);
0302 return m_representative->localToGlobal(gctx, surfaceLocal);
0303 }
0304
0305 std::vector<const IAxis*> getAxes() const override {
0306 return {&std::get<0>(m_axes), &std::get<1>(m_axes)};
0307 }
0308
0309 const Surface* surfaceRepresentation() const override {
0310 return m_representative.get();
0311 }
0312
0313 bool isValidBin(std::size_t globalBin) const override {
0314 const GridIndex indices = localBinsFromGlobalBin2D(globalBin);
0315 return isValidBin(indices);
0316 }
0317
0318 std::array<std::size_t, 2> numLocalBins() const override {
0319 return numLocalBins2D();
0320 }
0321
0322 SurfaceArray::NeighborWindow neighborWindow() const override {
0323 return m_neighborWindow;
0324 }
0325
0326 private:
0327 using GridIndex = std::array<std::size_t, 2>;
0328 using GridPoint = std::array<double, 2>;
0329
0330 using GridDistance = std::array<std::uint8_t, 2>;
0331
0332 using SurfacePackRange = std::pair<std::uint32_t, std::uint32_t>;
0333
0334 struct Crossing {
0335 Vector2 local;
0336 Vector3 global;
0337 };
0338
0339 using FillingGrid = std::vector<std::vector<const Surface*>>;
0340
0341
0342
0343 struct PackLookup {
0344 using is_transparent = void;
0345
0346 const std::vector<const Surface*>* storage = nullptr;
0347
0348 std::span<const Surface* const> pack(const SurfacePackRange& range) const {
0349 return {storage->data() + range.first, range.second};
0350 }
0351
0352 std::size_t operator()(std::span<const Surface* const> surfaces) const {
0353 std::size_t hash = surfaces.size();
0354 for (const Surface* surface : surfaces) {
0355 hash ^= std::hash<const Surface*>{}(surface) + 0x9e3779b97f4a7c15ULL +
0356 (hash << 6) + (hash >> 2);
0357 }
0358 return hash;
0359 }
0360 std::size_t operator()(const SurfacePackRange& range) const {
0361 return (*this)(pack(range));
0362 }
0363
0364 bool operator()(const SurfacePackRange& lhs,
0365 const SurfacePackRange& rhs) const {
0366 return std::ranges::equal(pack(lhs), pack(rhs));
0367 }
0368 bool operator()(std::span<const Surface* const> lhs,
0369 const SurfacePackRange& rhs) const {
0370 return std::ranges::equal(lhs, pack(rhs));
0371 }
0372 bool operator()(const SurfacePackRange& lhs,
0373 std::span<const Surface* const> rhs) const {
0374 return std::ranges::equal(pack(lhs), rhs);
0375 }
0376 };
0377
0378 std::shared_ptr<RegularSurface> m_representative;
0379 double m_tolerance{};
0380
0381 std::tuple<Axis1, Axis2> m_axes;
0382 std::vector<AxisDirection> m_binValues;
0383 SurfaceArray::NeighborWindow m_neighborWindow{};
0384 std::uint8_t m_overfill{};
0385
0386
0387 GridIndex m_numLocalBins{};
0388 std::size_t m_numGridBins{};
0389 std::size_t m_packStride1{};
0390 std::size_t m_packStridePerBin{};
0391 bool m_windowIsFixed{};
0392
0393 double m_gridScale = 1.;
0394
0395
0396
0397 std::vector<const Surface*> m_surfacePacks;
0398 std::vector<SurfacePackRange> m_neighborSurfacePacks;
0399
0400
0401 std::span<const Surface* const> surfacePack(std::size_t packIndex) const {
0402 const SurfacePackRange& range = m_neighborSurfacePacks[packIndex];
0403 return {m_surfacePacks.data() + range.first, range.second};
0404 }
0405
0406 bool isValidBin(const GridIndex& indices) const {
0407 const GridIndex nBins = numLocalBins2D();
0408 for (std::size_t i = 0; i < indices.size(); ++i) {
0409 const std::size_t idx = indices.at(i);
0410 if (idx <= 0 || idx >= nBins.at(i) + 1) {
0411 return false;
0412 }
0413 }
0414 return true;
0415 }
0416
0417 GridIndex numLocalBins2D() const { return m_numLocalBins; }
0418
0419 GridIndex localBinsFromPosition2D(const GridPoint& point) const {
0420 return detail::MultiAxisHelper::getLocalBinsFromPoint(point, m_axes);
0421 }
0422
0423 GridIndex localBinsFromGlobalBin2D(std::size_t globalBin) const {
0424 return detail::MultiAxisHelper::getLocalBinsFromGlobalBin(globalBin,
0425 m_axes);
0426 }
0427
0428 std::size_t globalBinFromLocalBins2D(const GridIndex& localBins) const {
0429 return detail::MultiAxisHelper::getGlobalBinFromLocalBins(localBins,
0430 m_axes);
0431 }
0432
0433 std::size_t neighborPackIndex(const GridIndex& localBins,
0434 const GridDistance& neighborDistance) const {
0435 const std::size_t globalGridBin =
0436 detail::MultiAxisHelper::getGlobalBinFromLocalBins(localBins, m_axes);
0437 return globalGridBin * m_packStridePerBin +
0438 neighborDistance[0] * m_packStride1 + neighborDistance[1];
0439 }
0440
0441 GridPoint binCenter(const GridIndex& localBins) const {
0442 return detail::MultiAxisHelper::getBinCenter(localBins, m_axes);
0443 }
0444
0445
0446
0447 std::optional<GridPoint> projectToGrid(const GeometryContext& gctx,
0448 const Vector3& position) const {
0449 const Vector3 normal = m_representative->normal(gctx, position);
0450 const std::optional<Crossing> crossing = findCrossing(
0451 gctx, position, normal, std::numeric_limits<double>::infinity());
0452 if (!crossing.has_value()) {
0453 return std::nullopt;
0454 }
0455 return surfaceToGridLocal(crossing->local);
0456 }
0457
0458
0459 template <typename Axis>
0460 std::uint8_t axisBinDistance(const Axis& axis, std::size_t from,
0461 double position, double step) const {
0462 if (step == 0.) {
0463 return 0;
0464 }
0465 const double extent = axis.getMax() - axis.getMin();
0466 if (!std::isfinite(step) || std::abs(step) >= extent) {
0467 return clampValue<std::uint8_t>(axis.getNBins());
0468 }
0469 double endpoint = position + step;
0470 const bool crossesSeam =
0471 endpoint < axis.getMin() || endpoint >= axis.getMax();
0472 if (axis.getBoundaryType() == AxisBoundaryType::Closed) {
0473
0474 if (crossesSeam) {
0475 endpoint =
0476 axis.getMin() +
0477 std::fmod(std::fmod(endpoint - axis.getMin(), extent) + extent,
0478 extent);
0479 }
0480 } else {
0481 endpoint = std::clamp(endpoint, axis.getMin(), axis.getMax());
0482 }
0483 const std::size_t nBins = axis.getNBins();
0484 const std::size_t a = std::clamp<std::size_t>(from, 1, nBins);
0485 const std::size_t b =
0486 std::clamp<std::size_t>(axis.getBin(endpoint), 1, nBins);
0487 std::size_t distance = a > b ? a - b : b - a;
0488 if (axis.getBoundaryType() == AxisBoundaryType::Closed) {
0489 distance = step >= 0 ? (b + nBins - a) % nBins : (a + nBins - b) % nBins;
0490
0491 if (distance == 0 && crossesSeam) {
0492 distance = nBins;
0493 }
0494 }
0495 return clampValue<std::uint8_t>(distance);
0496 }
0497
0498
0499 static constexpr double s_minIncidence = 1e-4;
0500
0501
0502
0503 GridDistance crossingNeighborDistance(const GeometryContext& gctx,
0504 const Crossing& crossing,
0505 const Vector3& direction,
0506 const GridPoint& gridLocal,
0507 const GridIndex& localBins) const {
0508 const GridDistance maximum = m_neighborWindow.max;
0509
0510
0511 if (m_windowIsFixed) {
0512 return maximum;
0513 }
0514
0515 if (m_tolerance == 0.) {
0516 return m_neighborWindow.min;
0517 }
0518
0519
0520
0521
0522 if (crossing.local[0] == 0. && m_representative->type() == Surface::Disc) {
0523 return maximum;
0524 }
0525
0526 const Vector3 normal = m_representative->normal(gctx, crossing.local);
0527 const double incidence = normal.dot(direction);
0528 if (std::abs(incidence) < s_minIncidence) {
0529 return maximum;
0530 }
0531
0532
0533 const double halfPath = m_tolerance / std::abs(incidence);
0534 const Vector3 slide = halfPath * (direction - incidence * normal);
0535
0536
0537 const Vector3 localSlide =
0538 m_representative->localToGlobalTransform(gctx).inverse().linear() *
0539 slide;
0540 const Vector2 boundSlide =
0541 m_representative->localCartesianToBoundLocalDerivative(
0542 gctx, crossing.global) *
0543 localSlide;
0544
0545 const GridPoint gridSlide = surfaceToGridLocal(boundSlide);
0546
0547 GridDistance neighborDistance{};
0548 for (const double side : {-1., 1.}) {
0549 neighborDistance[0] =
0550 std::max(neighborDistance[0],
0551 axisBinDistance(std::get<0>(m_axes), localBins[0],
0552 gridLocal[0], side * gridSlide[0]));
0553 neighborDistance[1] =
0554 std::max(neighborDistance[1],
0555 axisBinDistance(std::get<1>(m_axes), localBins[1],
0556 gridLocal[1], side * gridSlide[1]));
0557
0558 if (neighborDistance[0] >= maximum[0] &&
0559 neighborDistance[1] >= maximum[1]) {
0560 return maximum;
0561 }
0562 }
0563
0564 return {std::clamp(neighborDistance[0], m_neighborWindow.min[0],
0565 m_neighborWindow.max[0]),
0566 std::clamp(neighborDistance[1], m_neighborWindow.min[1],
0567 m_neighborWindow.max[1])};
0568 }
0569
0570
0571
0572
0573 void fillSurfaceFootprint(const GeometryContext& gctx, const Surface& surface,
0574 FillingGrid& fillingGrid) const {
0575
0576 const Vector3 reference =
0577 surface.referencePosition(gctx, AxisDirection::AxisR);
0578 const Vector3 referenceNormal = m_representative->normal(gctx, reference);
0579 if (!findCrossing(gctx, reference, referenceNormal, m_tolerance)
0580 .has_value()) {
0581 return;
0582 }
0583
0584
0585
0586
0587 constexpr unsigned int nSamples = 32;
0588
0589 const Polyhedron polyhedron =
0590 surface.polyhedronRepresentation(gctx, nSamples);
0591
0592
0593
0594 const bool firstAxisClosed =
0595 std::get<0>(m_axes).getBoundaryType() == AxisBoundaryType::Closed;
0596 const std::size_t iColumn = firstAxisClosed ? 0 : 1;
0597 const std::size_t iSpan = firstAxisClosed ? 1 : 0;
0598
0599
0600 std::map<std::size_t, std::pair<std::size_t, std::size_t>> columns;
0601 const auto addSample = [&](const Vector3& point) {
0602 const std::optional<GridPoint> gridLocal = projectToGrid(gctx, point);
0603 if (!gridLocal.has_value()) {
0604 return;
0605 }
0606 const GridIndex indices = localBinsFromPosition2D(*gridLocal);
0607 if (!isValidBin(indices)) {
0608 return;
0609 }
0610 const auto [it, inserted] =
0611 columns.try_emplace(indices[iColumn], indices[iSpan], indices[iSpan]);
0612 if (!inserted) {
0613 it->second.first = std::min(it->second.first, indices[iSpan]);
0614 it->second.second = std::max(it->second.second, indices[iSpan]);
0615 }
0616 };
0617
0618 for (const Polyhedron::FaceType& face : polyhedron.faces) {
0619 for (std::size_t i = 0; i < face.size(); ++i) {
0620 const Vector3& from = polyhedron.vertices.at(face.at(i));
0621 const Vector3& to =
0622 polyhedron.vertices.at(face.at((i + 1) % face.size()));
0623 for (std::size_t k = 0; k < nSamples; ++k) {
0624 const double t = static_cast<double>(k) / nSamples;
0625 addSample(from + t * (to - from));
0626 }
0627 }
0628 }
0629
0630 for (const auto& [column, span] : columns) {
0631 for (std::size_t i = span.first; i <= span.second; ++i) {
0632 GridIndex indices{};
0633 indices[iColumn] = column;
0634 indices[iSpan] = i;
0635 const int radius = m_overfill;
0636 const auto bins0 = std::get<0>(m_axes).neighborHoodIndices(
0637 indices[0], std::pair{-radius, radius});
0638 const auto bins1 = std::get<1>(m_axes).neighborHoodIndices(
0639 indices[1], std::pair{-radius, radius});
0640 for (const std::size_t bin0 : bins0) {
0641 for (const std::size_t bin1 : bins1) {
0642 if (isValidBin(GridIndex{bin0, bin1})) {
0643 fillingGrid.at(globalBinFromLocalBins2D({bin0, bin1}))
0644 .push_back(&surface);
0645 }
0646 }
0647 }
0648 }
0649 }
0650 }
0651
0652
0653 void populateNeighborCache(const FillingGrid& fillingGrid) {
0654 m_surfacePacks.clear();
0655 m_neighborSurfacePacks.assign(m_numGridBins * m_packStridePerBin, {0, 0});
0656
0657 std::vector<const Surface*> surfacePack;
0658 const PackLookup packLookup{&m_surfacePacks};
0659 std::unordered_set<SurfacePackRange, PackLookup, PackLookup> packRanges(
0660 0, packLookup, packLookup);
0661 for (std::size_t inputGlobalBin = 0; inputGlobalBin < fillingGrid.size();
0662 ++inputGlobalBin) {
0663 const GridIndex indices = localBinsFromGlobalBin2D(inputGlobalBin);
0664
0665 if (!isValidBin(indices)) {
0666 continue;
0667 }
0668
0669 for (int distance0 = 0; distance0 <= m_neighborWindow.max[0];
0670 ++distance0) {
0671 for (int distance1 = 0; distance1 <= m_neighborWindow.max[1];
0672 ++distance1) {
0673 surfacePack.clear();
0674
0675 const auto span0 = std::get<0>(m_axes).neighborHoodIndices(
0676 indices[0], std::pair<int, int>{-distance0, distance0});
0677 const auto span1 = std::get<1>(m_axes).neighborHoodIndices(
0678 indices[1], std::pair<int, int>{-distance1, distance1});
0679 for (const std::size_t bin0 : span0) {
0680 for (const std::size_t bin1 : span1) {
0681 const std::vector<const Surface*>& binContent =
0682 fillingGrid.at(globalBinFromLocalBins2D({bin0, bin1}));
0683 std::copy(binContent.begin(), binContent.end(),
0684 std::back_inserter(surfacePack));
0685 }
0686 }
0687
0688 std::ranges::sort(surfacePack);
0689 const auto last = std::ranges::unique(surfacePack);
0690 surfacePack.erase(last.begin(), last.end());
0691
0692 const std::size_t packIndex = neighborPackIndex(
0693 indices, {static_cast<std::uint8_t>(distance0),
0694 static_cast<std::uint8_t>(distance1)});
0695
0696 if (const auto it =
0697 packRanges.find(std::span<const Surface* const>(surfacePack));
0698 it != packRanges.end()) {
0699 m_neighborSurfacePacks[packIndex] = *it;
0700 } else {
0701 throw_assert(m_surfacePacks.size() + surfacePack.size() <=
0702 std::numeric_limits<std::uint32_t>::max(),
0703 "surface pack storage exceeds the 32 bit index range");
0704 const SurfacePackRange surfacePackRange = {
0705 static_cast<std::uint32_t>(m_surfacePacks.size()),
0706 static_cast<std::uint32_t>(surfacePack.size())};
0707 m_surfacePacks.insert(m_surfacePacks.end(), surfacePack.begin(),
0708 surfacePack.end());
0709 packRanges.insert(surfacePackRange);
0710 m_neighborSurfacePacks[packIndex] = surfacePackRange;
0711 }
0712 }
0713 }
0714 }
0715
0716 m_surfacePacks.shrink_to_fit();
0717 }
0718
0719 void checkGrid(std::span<const Surface* const> surfaces,
0720 const FillingGrid& fillingGrid) const {
0721 const std::set<const Surface*> allSurfaces(surfaces.begin(),
0722 surfaces.end());
0723
0724 std::set<const Surface*> seenSurface;
0725 for (const std::vector<const Surface*>& binSurfaces : fillingGrid) {
0726 seenSurface.insert(binSurfaces.begin(), binSurfaces.end());
0727 }
0728
0729 if (allSurfaces != seenSurface) {
0730 std::set<const Surface*> diff;
0731 std::ranges::set_difference(allSurfaces, seenSurface,
0732 std::inserter(diff, diff.begin()));
0733
0734 throw std::logic_error(std::format(
0735 "SurfaceArray grid does not contain all surfaces provided! "
0736 "{} surfaces not seen",
0737 diff.size()));
0738 }
0739 }
0740
0741 Vector2 gridToSurfaceLocal(const GridPoint& gridLocal) const {
0742 return {gridLocal[0] * m_gridScale, gridLocal[1]};
0743 }
0744
0745 GridPoint surfaceToGridLocal(const Vector2& local) const {
0746 return {local[0] / m_gridScale, local[1]};
0747 }
0748
0749
0750 std::optional<Crossing> findCrossing(const GeometryContext& gctx,
0751 const Vector3& position,
0752 const Vector3& direction,
0753 double tolerance) const {
0754 const Intersection3D intersection =
0755 m_representative
0756 ->intersect(gctx, position, direction,
0757 BoundaryTolerance::Infinite())
0758 .closest();
0759 if (!intersection.isValid() ||
0760 std::abs(intersection.pathLength()) > tolerance) {
0761 return std::nullopt;
0762 }
0763 const Vector3 global = intersection.position();
0764 return Crossing{
0765 m_representative->globalToLocal(gctx, global, direction).value(),
0766 global};
0767 }
0768
0769 std::optional<GridIndex> findLocalBin2D(const GeometryContext& gctx,
0770 const Vector3& position,
0771 const Vector3& direction,
0772 double tolerance) const {
0773 const std::optional<Crossing> crossing =
0774 findCrossing(gctx, position, direction, tolerance);
0775 if (!crossing.has_value()) {
0776 return std::nullopt;
0777 }
0778 const GridPoint gridLocal = surfaceToGridLocal(crossing->local);
0779 return localBinsFromPosition2D(gridLocal);
0780 }
0781 };
0782
0783 std::unique_ptr<SurfaceArray::ISurfaceGridLookup> makeSurfaceGridLookup(
0784 std::shared_ptr<RegularSurface> representative, double tolerance,
0785 std::tuple<const IAxis&, const IAxis&> axes,
0786 SurfaceArray::NeighborWindow neighborWindow, std::uint8_t overfill) {
0787 const auto& [iAxisA, iAxisB] = axes;
0788
0789 return iAxisA.visit([&]<typename axis_a_t>(const axis_a_t& axisA) {
0790 return iAxisB.visit(
0791 [&]<typename axis_b_t>(const axis_b_t& axisB)
0792 -> std::unique_ptr<SurfaceArray::ISurfaceGridLookup> {
0793 return std::make_unique<SurfaceGridLookupImpl<axis_a_t, axis_b_t>>(
0794 std::move(representative), tolerance,
0795 std::tuple<axis_a_t, axis_b_t>{axisA, axisB},
0796 std::vector<AxisDirection>(), neighborWindow, overfill);
0797 });
0798 });
0799 }
0800
0801 }
0802
0803 SurfaceArray::SurfaceArray(std::shared_ptr<const Surface> srf)
0804 : m_gridLookup(std::make_unique<SingleElementLookupImpl>(srf.get())),
0805 m_surfaces({std::move(srf)}) {
0806 m_surfacesRawPointers.push_back(m_surfaces.at(0).get());
0807 }
0808
0809 SurfaceArray::SurfaceArray(const GeometryContext& gctx,
0810 std::vector<std::shared_ptr<const Surface>> surfaces,
0811 std::shared_ptr<RegularSurface> representative,
0812 double tolerance,
0813 std::tuple<const IAxis&, const IAxis&> axes,
0814 NeighborWindow neighborWindow,
0815 std::uint8_t overfill) {
0816 m_gridLookup = makeSurfaceGridLookup(std::move(representative), tolerance,
0817 axes, neighborWindow, overfill);
0818 m_surfaces = std::move(surfaces);
0819 m_surfacesRawPointers =
0820 m_surfaces |
0821 std::views::transform(
0822 [](const std::shared_ptr<const Surface>& sp) { return sp.get(); }) |
0823 Ranges::to<std::vector>;
0824 m_gridLookup->fill(gctx, m_surfacesRawPointers);
0825 }
0826
0827 SurfaceArray::SurfaceArray(SurfaceArray&& other) noexcept = default;
0828
0829 SurfaceArray& SurfaceArray::operator=(SurfaceArray&& other) noexcept = default;
0830
0831 SurfaceArray::~SurfaceArray() = default;
0832
0833 std::span<const Surface* const> SurfaceArray::at(std::size_t bin) const {
0834 return m_gridLookup->at(bin);
0835 }
0836
0837 std::span<const Surface* const> SurfaceArray::at(
0838 const GeometryContext& gctx, const Vector3& position,
0839 const Vector3& direction) const {
0840 return m_gridLookup->at(gctx, position, direction);
0841 }
0842
0843 std::span<const Surface* const> SurfaceArray::neighbors(
0844 std::array<std::size_t, 2> gridIndices,
0845 std::uint8_t neighborDistance) const {
0846 return m_gridLookup->neighbors(gridIndices,
0847 {neighborDistance, neighborDistance});
0848 }
0849
0850 std::span<const Surface* const> SurfaceArray::neighbors(
0851 std::array<std::size_t, 2> gridIndices,
0852 std::array<std::uint8_t, 2> neighborDistance) const {
0853 return m_gridLookup->neighbors(gridIndices, neighborDistance);
0854 }
0855
0856 std::span<const Surface* const> SurfaceArray::neighbors(
0857 const GeometryContext& gctx, const Vector3& position,
0858 const Vector3& direction) const {
0859 return m_gridLookup->neighbors(gctx, position, direction);
0860 }
0861
0862 std::size_t SurfaceArray::size() const {
0863 return m_gridLookup->size();
0864 }
0865
0866 Vector3 SurfaceArray::getBinCenter(std::size_t bin) const {
0867 return m_gridLookup->getBinCenter(bin);
0868 }
0869
0870 std::vector<const IAxis*> SurfaceArray::getAxes() const {
0871 return m_gridLookup->getAxes();
0872 }
0873
0874 bool SurfaceArray::isValidBin(std::size_t bin) const {
0875 return m_gridLookup->isValidBin(bin);
0876 }
0877
0878 std::vector<AxisDirection> SurfaceArray::binningValues() const {
0879 return m_gridLookup->binningValues();
0880 }
0881
0882 std::ostream& SurfaceArray::toStream(const GeometryContext& ,
0883 std::ostream& sl) const {
0884 detail::OstreamStateGuard guard{sl};
0885 sl << std::fixed << std::setprecision(4);
0886 sl << "SurfaceArray:" << std::endl;
0887 sl << " - no surfaces: " << m_surfaces.size() << std::endl;
0888
0889 const std::vector<const IAxis*> axes = m_gridLookup->getAxes();
0890
0891 for (const auto [j, axis] : enumerate(axes)) {
0892 const AxisBoundaryType bdt = axis->getBoundaryType();
0893 sl << " - axis " << (j + 1) << std::endl;
0894 sl << " - boundary type: ";
0895 if (bdt == AxisBoundaryType::Open) {
0896 sl << "open";
0897 }
0898 if (bdt == AxisBoundaryType::Bound) {
0899 sl << "bound";
0900 }
0901 if (bdt == AxisBoundaryType::Closed) {
0902 sl << "closed";
0903 }
0904 sl << std::endl;
0905 sl << " - type: " << (axis->isEquidistant() ? "equidistant" : "variable")
0906 << std::endl;
0907 sl << " - n bins: " << axis->getNBins() << std::endl;
0908 sl << " - bin edges: [ ";
0909 const std::vector<double> binEdges = axis->getBinEdges();
0910 for (const auto [i, binEdge] : enumerate(binEdges)) {
0911 if (i > 0) {
0912 sl << ", ";
0913 }
0914
0915 sl << ((std::abs(binEdge) >= 5e-4) ? binEdge : 0.0);
0916 }
0917 sl << " ]" << std::endl;
0918 }
0919 return sl;
0920 }
0921
0922 const Surface* SurfaceArray::surfaceRepresentation() const {
0923 return m_gridLookup->surfaceRepresentation();
0924 }
0925
0926 std::array<std::size_t, 2> SurfaceArray::numLocalBins() const {
0927 return m_gridLookup->numLocalBins();
0928 }
0929
0930 SurfaceArray::NeighborWindow SurfaceArray::neighborWindow() const {
0931 return m_gridLookup->neighborWindow();
0932 }
0933
0934 std::uint8_t SurfaceArray::maxNeighborDistance() const {
0935 const NeighborWindow window = m_gridLookup->neighborWindow();
0936 return std::max(window.max[0], window.max[1]);
0937 }
0938
0939 }