-
Notifications
You must be signed in to change notification settings - Fork 256
Expand file tree
/
Copy pathDetrayPayloadConverter.cpp
More file actions
1031 lines (870 loc) · 38.8 KB
/
Copy pathDetrayPayloadConverter.cpp
File metadata and controls
1031 lines (870 loc) · 38.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This file is part of the ACTS project.
//
// Copyright (C) 2016 CERN for the benefit of the ACTS project
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
#include "ActsPlugins/Detray/DetrayPayloadConverter.hpp"
#include "Acts/Definitions/Common.hpp"
#include "Acts/Geometry/CompositePortalLink.hpp"
#include "Acts/Geometry/GeometryContext.hpp"
#include "Acts/Geometry/GeometryIdentifier.hpp"
#include "Acts/Geometry/GridPortalLink.hpp"
#include "Acts/Geometry/TrackingGeometry.hpp"
#include "Acts/Geometry/TrivialPortalLink.hpp"
#include "Acts/Geometry/VolumeBounds.hpp"
#include "Acts/Material/ISurfaceMaterial.hpp"
#include "Acts/Navigation/INavigationPolicy.hpp"
#include "Acts/Surfaces/AnnulusBounds.hpp"
#include "Acts/Surfaces/CylinderBounds.hpp"
#include "Acts/Surfaces/RadialBounds.hpp"
#include "Acts/Surfaces/RectangleBounds.hpp"
#include "Acts/Surfaces/Surface.hpp"
#include "Acts/Surfaces/SurfaceBounds.hpp"
#include "Acts/Surfaces/TrapezoidBounds.hpp"
#include "Acts/Utilities/Helpers.hpp"
#include "Acts/Utilities/Logger.hpp"
#include "ActsPlugins/Detray/DetrayConversionUtils.hpp"
#include <optional>
#include <detray/geometry/shapes/annulus2D.hpp>
#include <detray/geometry/shapes/concentric_cylinder2D.hpp>
#include <detray/geometry/shapes/cylinder2D.hpp>
#include <detray/geometry/shapes/rectangle2D.hpp>
#include <detray/geometry/shapes/ring2D.hpp>
#include <detray/geometry/shapes/trapezoid2D.hpp>
#include <detray/io/frontend/definitions.hpp>
#include <detray/io/frontend/payloads.hpp>
using namespace Acts;
namespace ActsPlugins {
DetrayPayloadConverter::DetrayPayloadConverter(
const Config& config, std::unique_ptr<const Logger> logger)
: m_cfg(config), m_logger(std::move(logger)) {}
namespace {
using enum detray::io::shape_id;
detray::io::mask_payload convertBounds(const AnnulusBounds& annulus) {
using enum detray::annulus2D::boundaries;
using enum AnnulusBounds::BoundValues;
detray::io::mask_payload payload;
payload.shape = annulus2;
payload.boundaries.resize(e_size);
payload.boundaries.at(e_min_r) = annulus.get(eMinR);
payload.boundaries.at(e_max_r) = annulus.get(eMaxR);
payload.boundaries.at(e_min_phi_rel) = annulus.get(eMinPhiRel);
payload.boundaries.at(e_max_phi_rel) = annulus.get(eMaxPhiRel);
payload.boundaries.at(e_average_phi) = annulus.get(eAveragePhi);
payload.boundaries.at(e_shift_x) = annulus.get(eOriginX);
payload.boundaries.at(e_shift_y) = annulus.get(eOriginY);
return payload;
}
detray::io::mask_payload convertBounds(const RectangleBounds& rectangle) {
using enum RectangleBounds::BoundValues;
using enum detray::rectangle2D::boundaries;
detray::io::mask_payload payload;
payload.shape = rectangle2;
payload.boundaries.resize(e_size);
double minX = rectangle.get(eMinX);
double maxX = rectangle.get(eMaxX);
double minY = rectangle.get(eMinY);
double maxY = rectangle.get(eMaxY);
if (minX != -maxX || minY != -maxY) {
throw std::runtime_error(
"Rectangle bounds are not symmetric, detray cannot handle this");
}
payload.boundaries.at(e_half_x) = maxX;
payload.boundaries.at(e_half_y) = maxY;
return payload;
}
detray::io::mask_payload convertBounds(const CylinderBounds& cylinder,
detray::io::shape_id shape) {
using enum CylinderBounds::BoundValues;
detray::io::mask_payload payload;
payload.shape = shape;
if (shape == portal_cylinder2) {
using enum detray::concentric_cylinder2D::boundaries;
payload.boundaries.resize(e_size);
payload.boundaries.at(e_r) = cylinder.get(eR);
double hlZ = cylinder.get(eHalfLengthZ);
payload.boundaries.at(e_lower_z) = -hlZ;
payload.boundaries.at(e_upper_z) = hlZ;
} else {
using enum detray::cylinder2D::boundaries;
payload.boundaries.resize(e_size);
payload.boundaries.at(e_r) = cylinder.get(eR);
double hlZ = cylinder.get(eHalfLengthZ);
payload.boundaries.at(e_lower_z) = -hlZ;
payload.boundaries.at(e_upper_z) = hlZ;
}
return payload;
}
detray::io::mask_payload convertBounds(const TrapezoidBounds& trapezoid) {
using enum TrapezoidBounds::BoundValues;
using enum detray::trapezoid2D::boundaries;
detray::io::mask_payload payload;
payload.shape = trapezoid2;
payload.boundaries.resize(e_size);
payload.boundaries.at(e_half_length_0) = trapezoid.get(eHalfLengthXnegY);
payload.boundaries.at(e_half_length_1) = trapezoid.get(eHalfLengthXposY);
payload.boundaries.at(e_half_length_2) = trapezoid.get(eHalfLengthY);
payload.boundaries.at(e_divisor) = 1 / (2 * trapezoid.get(eHalfLengthY));
return payload;
}
detray::io::mask_payload convertBounds(const RadialBounds& radial) {
using enum RadialBounds::BoundValues;
using enum detray::ring2D::boundaries;
if (!radial.coversFullAzimuth()) {
throw std::runtime_error(
"Radial bounds do not cover full azimuth, detray cannot handle this");
}
if (radial.get(eAveragePhi) != 0.) {
throw std::runtime_error(
"Radial bounds have an average phi, detray cannot handle this");
}
detray::io::mask_payload payload;
payload.shape = ring2;
payload.boundaries.resize(e_size);
payload.boundaries.at(e_inner_r) = radial.get(eMinR);
payload.boundaries.at(e_outer_r) = radial.get(eMaxR);
return payload;
}
} // namespace
detray::io::mask_payload DetrayPayloadConverter::convertMask(
const SurfaceBounds& bounds, bool forPortal) {
detray::io::mask_payload payload;
switch (bounds.type()) {
using enum SurfaceBounds::BoundsType;
using enum detray::io::shape_id;
case eAnnulus:
payload = convertBounds(dynamic_cast<const AnnulusBounds&>(bounds));
break;
case eRectangle:
payload = convertBounds(dynamic_cast<const RectangleBounds&>(bounds));
break;
case eCylinder:
payload = convertBounds(dynamic_cast<const CylinderBounds&>(bounds),
forPortal ? portal_cylinder2 : cylinder2);
break;
case eTrapezoid:
payload = convertBounds(dynamic_cast<const TrapezoidBounds&>(bounds));
break;
case eDisc:
if (auto* radial = dynamic_cast<const RadialBounds*>(&bounds);
radial != nullptr) {
payload = convertBounds(*radial);
} else {
throw std::runtime_error(
"Disc bounds type but not radial bounds currently unsupported");
}
break;
default:
payload.shape = unknown;
break;
}
return payload;
}
detray::io::surface_payload DetrayPayloadConverter::convertSurface(
const GeometryContext& gctx, const Surface& surface, bool portal) const {
detray::io::surface_payload payload;
payload.transform = DetrayConversionUtils::convertTransform(
surface.localToGlobalTransform(gctx));
payload.source = surface.geometryId().value();
payload.identifier = std::nullopt;
bool isSensitive = false;
if (m_cfg.sensitiveStrategy ==
DetrayPayloadConverter::Config::SensitiveStrategy::Identifier) {
isSensitive = surface.geometryId().sensitive() > 0;
} else {
isSensitive = surface.isSensitive();
}
if (portal) {
payload.type = detray::surface_id::e_portal;
} else {
payload.type = isSensitive ? detray::surface_id::e_sensitive
: detray::surface_id::e_passive;
}
payload.masks = {convertMask(surface.bounds(), portal)};
return payload;
}
detray::io::volume_payload DetrayPayloadConverter::convertVolume(
const GeometryContext& gctx, const TrackingVolume& volume) const {
detray::io::volume_payload payload;
payload.transform = DetrayConversionUtils::convertTransform(
volume.localToGlobalTransform(gctx));
payload.name = volume.volumeName();
switch (volume.volumeBounds().type()) {
using enum VolumeBounds::BoundsType;
using enum detray::volume_id;
case eCylinder:
payload.type = e_cylinder;
break;
case eCuboid:
payload.type = e_cuboid;
break;
case eTrapezoid:
payload.type = e_trapezoid;
break;
case eCone:
payload.type = e_cone;
break;
default:
payload.type = e_unknown;
break;
}
return payload;
}
namespace {
std::vector<const TrivialPortalLink*> decomposeToTrivials(
const PortalLinkBase& link, const Logger& logger) {
std::vector<const TrivialPortalLink*> trivials;
if (auto* trivial = dynamic_cast<const TrivialPortalLink*>(&link);
trivial != nullptr) {
trivials.push_back(trivial);
} else if (auto* composite = dynamic_cast<const CompositePortalLink*>(&link);
composite != nullptr) {
ACTS_VERBOSE("Converting composite portal link with "
<< composite->links().size() << " sub-links");
for (const auto& subLink : composite->links()) {
const auto* subTrivial = dynamic_cast<const TrivialPortalLink*>(&subLink);
if (subTrivial == nullptr) {
throw std::runtime_error(
"Composite portal link contains non-trivial portal links");
} else {
trivials.push_back(subTrivial);
}
}
} else if (auto* grid = dynamic_cast<const GridPortalLink*>(&link);
grid != nullptr) {
ACTS_VERBOSE("Converting grid portal link with "
<< grid->artifactPortalLinks().size() << " link artifacts");
for (const auto& artifact : grid->artifactPortalLinks()) {
trivials.push_back(&artifact);
}
} else {
throw std::runtime_error(
"Unknown portal link type, detray cannot handle this");
}
return trivials;
}
/// Build a portal mask for a trivial child surface, expressed in the frame of
/// the parent (merged) portal surface. This allows a single detray portal
/// surface to carry one mask per neighbour volume (multi-mask portal).
///
/// Only concentric cylinders (z-binning) and rings/discs (r-binning) can be
/// folded onto a single shared transform; everything else returns nullopt so
/// the caller can fall back to emitting one surface per trivial.
///
/// @param gctx the geometry context
/// @param parent the merged portal surface that provides the shared transform
/// @param child the trivial child surface whose sub-region is encoded
/// @returns the mask payload in the parent frame, or nullopt if unsupported
std::optional<detray::io::mask_payload> convertPortalMask(
const GeometryContext& gctx, const Surface& parent, const Surface& child) {
using enum detray::io::shape_id;
detray::io::mask_payload payload =
DetrayPayloadConverter::convertMask(child.bounds(), true);
if (payload.shape == portal_cylinder2) {
// The child z-range is centered on the child transform: shift it into the
// parent frame. The detray reader folds the (parent) surface-transform z
// back into every mask, so this reconstructs the correct global z-range.
using enum detray::concentric_cylinder2D::boundaries;
const double dz = (parent.localToGlobalTransform(gctx).inverse() *
child.localToGlobalTransform(gctx))
.translation()[eZ];
payload.boundaries.at(e_lower_z) += dz;
payload.boundaries.at(e_upper_z) += dz;
return payload;
} else if (payload.shape == ring2) {
// Ring boundaries are inner_r/outer_r only and frame-independent for
// coplanar r-split children.
return payload;
}
return std::nullopt;
}
/// Check compatibility between ACTS surface type and detray material_id
/// @returns pair<is_compatible, expected_material_id>
std::pair<bool, detray::io::material_id> isGridMaterialCompatible(
Surface::SurfaceType surfaceType, detray::io::material_id materialId) {
using enum Surface::SurfaceType;
using enum detray::io::material_id;
switch (surfaceType) {
case Cylinder:
return {materialId == concentric_cylinder2_map, concentric_cylinder2_map};
case Disc:
return {materialId == ring2_map, ring2_map};
case Plane:
return {materialId == rectangle2_map, rectangle2_map};
default:
// For other surface types, we don't have specific checks yet
return {true, unknown};
}
}
} // namespace
void DetrayPayloadConverter::handlePortalLink(
const GeometryContext& gctx, const TrackingVolume& volume,
detray::io::volume_payload& volPayload,
const std::function<std::size_t(const TrackingVolume*)>& volumeLookup,
std::unordered_map<const Surface*, std::size_t>& surfaceIndices,
const PortalLinkBase& link) const {
std::vector<const TrivialPortalLink*> trivials =
decomposeToTrivials(link, logger());
// If ANY of the trivials point at the current volume, we don't handle this
// portal link at all, otherwise we would get a self-referencing volume link
if (std::ranges::any_of(
trivials, [&](const auto* t) { return &t->volume() == &volume; })) {
ACTS_VERBOSE("At least one trivial link points at this volume ("
<< volume.volumeName() << ") => skipping");
return;
}
// Multi-mask portal: a single detray portal surface can carry one mask per
// neighbour volume. This is possible for concentric cylinders (z-binning)
// and rings/discs (r-binning), where all sub-regions share the parent
// surface transform. For everything else we fall back to one surface per
// trivial below.
const Surface& parentSurface = link.surface();
const bool canMultiMask =
trivials.size() > 1 &&
(parentSurface.type() == Surface::SurfaceType::Cylinder ||
parentSurface.type() == Surface::SurfaceType::Disc);
if (canMultiMask) {
std::vector<detray::io::mask_payload> masks;
masks.reserve(trivials.size());
bool ok = true;
for (const auto* trivial : trivials) {
auto mask = convertPortalMask(gctx, parentSurface, trivial->surface());
if (!mask.has_value()) {
ok = false;
break;
}
mask->volume_link.link = volumeLookup(&trivial->volume());
masks.push_back(*mask);
}
if (ok) {
ACTS_VERBOSE("Converting " << trivials.size()
<< " trivial portal links into a single "
"multi-mask portal in volume "
<< volume.volumeName());
// Reuse convertSurface for the parent transform / source / portal type,
// then replace its single mask with the per-trivial masks.
auto& srfPayload = volPayload.surfaces.emplace_back(
convertSurface(gctx, parentSurface, true));
srfPayload.index_in_coll = volPayload.surfaces.size() - 1;
srfPayload.masks = std::move(masks);
// All trivial child surfaces map to this single detray surface index so
// that material assignment can resolve them later.
for (const auto* trivial : trivials) {
surfaceIndices[&trivial->surface()] = srfPayload.index_in_coll.value();
}
return;
}
ACTS_VERBOSE("Multi-mask portal conversion not possible for volume "
<< volume.volumeName()
<< " => falling back to one surface per trivial");
}
for (const auto* trivial : trivials) {
ACTS_VERBOSE("Converting trivial portal link registered to volume "
<< volume.volumeName());
ACTS_VERBOSE(
"Portal link surface is: " << trivial->surface().toStream(gctx));
if (&trivial->volume() == &volume) {
ACTS_VERBOSE("~> points at this volume (" << volume.volumeName()
<< ") => skipping");
return;
}
ACTS_VERBOSE("~> points at different volume ("
<< trivial->volume().volumeName()
<< ") => adding link to this volume (" << volume.volumeName()
<< ")");
// add the surface (including mask first)
auto& srfPayload = volPayload.surfaces.emplace_back(
convertSurface(gctx, trivial->surface(), true));
srfPayload.index_in_coll = volPayload.surfaces.size() - 1;
// lookup the target volume index (we already converted this)
ACTS_VERBOSE("Target volume index for "
<< trivial->volume().volumeName() << ": "
<< volumeLookup(&trivial->volume()));
std::size_t targetVolumeIndex = volumeLookup(&trivial->volume());
srfPayload.masks.at(0).volume_link.link = targetVolumeIndex;
surfaceIndices[&trivial->surface()] = srfPayload.index_in_coll.value();
}
}
void DetrayPayloadConverter::makeEndOfWorld(
const GeometryContext& gctx, detray::io::volume_payload& volPayload,
std::unordered_map<const Surface*, std::size_t>& surfaceIndices,
const Surface& surface) const {
ACTS_VERBOSE("Adding end of world surface");
auto& srfPayload =
volPayload.surfaces.emplace_back(convertSurface(gctx, surface, true));
srfPayload.index_in_coll = volPayload.surfaces.size() - 1;
// Marker for end of world is MAX
srfPayload.masks.at(0).volume_link.link =
std::numeric_limits<std::size_t>::max();
surfaceIndices[&surface] = srfPayload.index_in_coll.value();
}
void DetrayPayloadConverter::handlePortal(
const GeometryContext& gctx, const TrackingVolume& volume,
detray::io::volume_payload& volPayload,
const std::function<std::size_t(const TrackingVolume*)>& volumeLookup,
std::unordered_map<const Surface*, std::size_t>& surfaceIndices,
const Portal& portal) const {
auto* lAlong = portal.getLink(Direction::AlongNormal());
auto* lOpposite = portal.getLink(Direction::OppositeNormal());
if (lAlong == nullptr && lOpposite == nullptr) {
// Sanity check: this shouldn't happen
throw std::runtime_error("Portal link is not symmetric");
}
if (lAlong != nullptr) {
handlePortalLink(gctx, volume, volPayload, volumeLookup, surfaceIndices,
*lAlong);
} else {
// can't both be nullptr
assert(lOpposite != nullptr);
makeEndOfWorld(gctx, volPayload, surfaceIndices, lOpposite->surface());
}
if (lOpposite != nullptr) {
handlePortalLink(gctx, volume, volPayload, volumeLookup, surfaceIndices,
*lOpposite);
} else {
// can't both be nullptr
assert(lAlong != nullptr);
makeEndOfWorld(gctx, volPayload, surfaceIndices, lAlong->surface());
}
}
namespace {
constexpr static detray::io::surface_material_payload s_dummyMaterialSlab{
.type = detray::io::material_id::slab,
.index_in_coll = std::numeric_limits<std::size_t>::max(),
.thickness = 42,
.mat = {42, 42, 42, 42, 42, 42, 42},
};
} // namespace
std::pair<std::vector<detray::io::grid_payload<
detray::io::surface_material_payload, detray::io::material_id>>,
detray::io::material_volume_payload>
DetrayPayloadConverter::convertMaterial(
const TrackingVolume& volume,
const std::unordered_map<const Surface*, std::size_t>& surfaceIndices,
detray::io::volume_payload& volPayload) const {
ACTS_DEBUG("Converting material for volume " << volume.volumeName());
std::vector<detray::io::grid_payload<detray::io::surface_material_payload,
detray::io::material_id>>
grids;
detray::io::material_volume_payload homogeneous;
homogeneous.volume_link.link = volPayload.index.link;
// @HACK: Detray does not like homoegeneous material only on SOME surfaces.
ACTS_INFO("Adding dummy material slabs to homogeneous collection for "
<< volPayload.surfaces.size()
<< " surfaces (detray "
"hack)");
for (const auto& surface : volPayload.surfaces) {
auto& slabPayload =
homogeneous.surface_mat.emplace_back(s_dummyMaterialSlab);
slabPayload.index_in_coll = surface.index_in_coll.value();
slabPayload.surface.link = surface.index_in_coll.value();
}
std::map<std::size_t, const ISurfaceMaterial*> srfIdxToMaterial;
auto assignMaterial = [&](const ISurfaceMaterial* material,
DetraySurfaceMaterial& detrayMaterial,
std::size_t srfIdx, const Surface& surface) {
auto handleHomogeneous =
[&](const detray::io::surface_material_payload& slab) {
// Given the pseudo slabs from before, we need to either update an
// existing slab or create a new one.
auto it = std::ranges::find_if(
homogeneous.surface_mat, [srfIdx](const auto& matslab) {
return matslab.surface.link == srfIdx;
});
if (it != homogeneous.surface_mat.end()) {
ACTS_VERBOSE("Adding slab to homogeneous material for surface "
<< srfIdx);
auto& targetSlab = *it;
targetSlab = slab;
targetSlab.index_in_coll = srfIdx;
targetSlab.surface.link = srfIdx;
} else {
ACTS_VERBOSE("Updating slab in homogeneous material for surface "
<< srfIdx);
homogeneous.surface_mat.emplace_back(slab);
homogeneous.surface_mat.back().index_in_coll = srfIdx;
homogeneous.surface_mat.back().surface.link = srfIdx;
}
auto sit = srfIdxToMaterial.find(srfIdx);
if (sit != srfIdxToMaterial.end() && sit->second != material) {
ACTS_ERROR("Surface "
<< srfIdx
<< " already has a different material assigned");
throw std::runtime_error("Material mismatch for surface");
}
};
auto handleGrid = [&](const detray::io::grid_payload<
detray::io::surface_material_payload,
detray::io::material_id>& grid) {
ACTS_DEBUG("Assigning grid material to surface " << srfIdx);
auto it = srfIdxToMaterial.find(srfIdx);
if (it != srfIdxToMaterial.end()) {
// Surface already has some material assigned
if (it->second != material) {
ACTS_ERROR("Surface "
<< srfIdx << " already has a different material assigned");
throw std::runtime_error("Material mismatch for surface");
}
// It's the same material again, we just skip adding a duplicate
return;
}
// @TODO: Add a consistency check between the surface type and the
// axis types: detray's consistency check will find this but
// the error message is not trivial. In this location, we can
// print out the surface id and guide debugging!
// Check compatibility between surface type and grid material_id
auto [isCompatible, expectedId] =
isGridMaterialCompatible(surface.type(), grid.grid_link.type);
if (!isCompatible) {
ACTS_ERROR("Grid material compatibility error for surface "
<< surface.geometryId() << " (index " << srfIdx << "): "
<< "Surface type " << surface.type()
<< " is incompatible with material_id '"
<< toUnderlying(grid.grid_link.type) << "'. Expected '"
<< toUnderlying(expectedId)
<< "'. This indicates the BinUtility axis definition "
<< "does not match the surface geometry.");
throw std::runtime_error(
"Grid material has incompatible axis definition for surface type");
}
grids.emplace_back(grid);
grids.back().owner_link.link = srfIdx;
};
std::visit(overloaded{handleHomogeneous, handleGrid}, detrayMaterial);
srfIdxToMaterial[srfIdx] = material;
};
auto printSurfaceInfo = [&](DetraySurfaceMaterial& detrayMaterial,
const Surface& surface) {
auto handleHomogeneous = [&](const detray::io::surface_material_payload&) {
ACTS_VERBOSE("Surface " << surface.geometryId()
<< " has homogeneous material");
};
auto handleGrid =
[&](const detray::io::grid_payload<detray::io::surface_material_payload,
detray::io::material_id>&) {
ACTS_VERBOSE("Surface " << surface.geometryId()
<< " has grid material");
};
std::visit(overloaded{handleHomogeneous, handleGrid}, detrayMaterial);
};
ACTS_VERBOSE("Looping over " << volume.surfaces().size()
<< " surfaces in volume " << volPayload.name);
for (const auto& surface : volume.surfaces()) {
auto srfIt = surfaceIndices.find(&surface);
if (srfIt == surfaceIndices.end()) {
ACTS_ERROR("Surface " << surface.geometryId().value()
<< " not found in volume " << volPayload.name
<< ". This is a bug in the conversion.");
throw std::runtime_error("Surface not found in volume");
}
std::size_t srfIdx = srfIt->second;
if (!surface.hasMaterial()) {
continue;
}
std::optional detrayMaterial =
m_cfg.convertSurfaceMaterial(*surface.surfaceMaterial(), surface);
if (!detrayMaterial.has_value()) {
continue;
}
printSurfaceInfo(*detrayMaterial, surface);
assignMaterial(surface.surfaceMaterial(), *detrayMaterial, srfIdx, surface);
}
ACTS_VERBOSE("Looping over " << volume.portals().size()
<< " portals in volume " << volPayload.name);
// Portals need special treatment: we have decomposed them to their trivial
// portal links, and only registered a subset to them as well. We again need
// to decompose here, and only look for the portals that are actually found in
// the volume payload
for (const auto& portal : volume.portals()) {
// First check, if the combined portal surface has material assigned at all,
// if not there's nothing to do
const auto* surfaceMaterial = portal.surface().surfaceMaterial();
if (surfaceMaterial == nullptr) {
continue;
}
std::optional detrayMaterial =
m_cfg.convertSurfaceMaterial(*surfaceMaterial, portal.surface());
// Portal surface material reports it does not apply to detray, skip
if (!detrayMaterial.has_value()) {
continue;
}
printSurfaceInfo(*detrayMaterial, portal.surface());
// Have valid detray material, now we need to find the surfaces that are
// actually there in detray
for (auto dir : {Direction::AlongNormal(), Direction::OppositeNormal()}) {
const auto* link = portal.getLink(dir);
if (link == nullptr) {
continue;
}
ACTS_VERBOSE("Processing dir=" << dir << " for portal on surface "
<< portal.surface().geometryId());
std::vector<const TrivialPortalLink*> trivials =
decomposeToTrivials(*link, logger());
ACTS_VERBOSE("Portal link in volume "
<< volPayload.name << " has produced " << trivials.size()
<< " trivials");
for (const auto* trivial : trivials) {
auto srfIt = surfaceIndices.find(&trivial->surface());
if (srfIt == surfaceIndices.end()) {
// This trivial was not converted, skip
continue;
}
std::size_t srfIdx = srfIt->second;
ACTS_VERBOSE("Trivial portal link in volume "
<< volPayload.name << " has surface "
<< trivial->surface().geometryId() << " detray idx "
<< srfIdx);
// Assign (a copy of) the detray material to the surface payload
// associated with this trivial
assignMaterial(surfaceMaterial, *detrayMaterial, srfIdx,
trivial->surface());
}
}
}
return {grids, homogeneous};
}
DetrayPayloadConverter::Payloads
DetrayPayloadConverter::convertTrackingGeometry(
const GeometryContext& gctx, const TrackingGeometry& geometry) const {
ACTS_INFO("Converting tracking geometry to detray format");
if (geometry.geometryVersion() != TrackingGeometry::GeometryVersion::Gen3) {
ACTS_WARNING(
"Only Gen3 tracking geometries are supported. Gen1 geometries will "
"give wrong results");
}
if (m_cfg.beampipeVolume == nullptr) {
throw std::runtime_error(
"Beampipe volume not set. This is needed to ensure detray receives the "
"beampip volume where it expects it");
}
Payloads payloads;
payloads.detector = std::make_unique<detray::io::detector_payload>();
detray::io::detector_payload& detPayload = *payloads.detector;
payloads.homogeneousMaterial =
std::make_unique<detray::io::detector_homogeneous_material_payload>();
detray::io::detector_homogeneous_material_payload& dthmPayload =
*payloads.homogeneousMaterial;
payloads.materialGrids = std::make_unique<detray::io::detector_grids_payload<
detray::io::surface_material_payload, detray::io::material_id>>();
detray::io::detector_grids_payload<detray::io::surface_material_payload,
detray::io::material_id>& materialGrids =
*payloads.materialGrids;
payloads.surfaceGrids = std::make_unique<
detray::io::detector_grids_payload<std::size_t, detray::io::accel_id>>();
detray::io::detector_grids_payload<std::size_t, detray::io::accel_id>&
surfaceGrids = *payloads.surfaceGrids;
std::unordered_map<const TrackingVolume*, std::size_t> volumeIds;
auto lookup = [&volumeIds](const TrackingVolume* v) {
return volumeIds.at(v);
};
std::unordered_map<const TrackingVolume*,
std::unordered_map<const Surface*, std::size_t>>
volumeSurfaceIndices;
geometry.apply([&](const TrackingVolume& volume) {
auto& volPayload =
detPayload.volumes.emplace_back(convertVolume(gctx, volume));
volPayload.index.link = detPayload.volumes.size() - 1;
volumeIds[&volume] = volPayload.index.link;
ACTS_DEBUG("Volume " << volume.volumeName() << " has index "
<< volPayload.index.link);
auto& surfaceIndices = volumeSurfaceIndices[&volume];
for (auto& surface : volume.surfaces()) {
auto& srfPayload =
volPayload.surfaces.emplace_back(convertSurface(gctx, surface));
srfPayload.index_in_coll = volPayload.surfaces.size() - 1;
srfPayload.masks.at(0).volume_link.link = volPayload.index.link;
surfaceIndices[&surface] = srfPayload.index_in_coll.value();
}
});
// Run again over volumes, can lookup volume index from pointer now
geometry.apply([&](const TrackingVolume& volume) {
auto& volPayload = detPayload.volumes.at(volumeIds.at(&volume));
auto& surfaceIndices = volumeSurfaceIndices[&volume];
for (const auto& portal : volume.portals()) {
handlePortal(gctx, volume, volPayload, lookup, surfaceIndices, portal);
}
ACTS_DEBUG("Volume " << volume.volumeName() << " (detray idx: "
<< volPayload.index.link << ") has "
<< volPayload.surfaces.size() << " total surfaces");
std::size_t nPortals =
std::ranges::count_if(volPayload.surfaces, [](const auto& srfPayload) {
return srfPayload.type == detray::surface_id::e_portal;
});
ACTS_DEBUG("-> portals: " << nPortals);
std::size_t nSensitives =
std::ranges::count_if(volPayload.surfaces, [](const auto& srfPayload) {
return srfPayload.type == detray::surface_id::e_sensitive;
});
ACTS_DEBUG("-> sensitives: " << nSensitives);
ACTS_DEBUG("-> other surfaces: " << volPayload.surfaces.size() - nPortals -
nSensitives);
for (const auto& [surface, idx] : surfaceIndices) {
ACTS_VERBOSE("Surface " << surface->geometryId() << " (&: " << surface
<< ", type: " << surface->type()
<< ") has detray index " << idx);
}
// Portals have produced surfaces and are added in volume payload, handle
// material now
auto [grids, homogeneous] =
convertMaterial(volume, surfaceIndices, volPayload);
ACTS_DEBUG("Volume " << volume.volumeName()
<< " (detray idx: " << volPayload.index.link
<< ") has " << homogeneous.surface_mat.size()
<< " material slabs");
if (!homogeneous.surface_mat.empty()) {
// Only add if it's not empty (it might be)
// NOTE: Currently, it'll always be populated by at least the homogeneous
// NOTE: Volume association is internal to
// `detray::io::material_volume_payload`
dthmPayload.volumes.emplace_back(std::move(homogeneous));
}
ACTS_DEBUG("Volume " << volume.volumeName()
<< " (detray idx: " << volPayload.index.link
<< ") has " << grids.size() << " material grids");
if (!grids.empty()) {
// Only add if we have grids
// NOTE: Volume association is EXTERNAL, i.e. we need to fill a map keyed
// by the volume index
materialGrids.grids[volPayload.index.link] = std::move(grids);
}
// Look for navigation policies that we need to convert!
const auto* navPolicy = volume.navigationPolicy();
if (navPolicy != nullptr) {
// Create surface lookup function for this volume
auto surfaceLookupFn =
[&surfaceIndices](const Surface* surface) -> std::size_t {
auto it = surfaceIndices.find(surface);
if (it == surfaceIndices.end()) {
throw std::runtime_error("Surface not found in surface indices map");
}
return it->second;
};
std::optional<DetraySurfaceGrid> detrayGrid = std::nullopt;
navPolicy->visit([&](const INavigationPolicy& policy) {
if (detrayGrid.has_value()) {
ACTS_ERROR("Volume "
<< volume.volumeName()
<< " has more than one detray-convertible navigation "
"policy. This cannot currently be handled.");
throw std::runtime_error{
"Multiple detray-compatible navigation policies"};
}
detrayGrid = m_cfg.convertNavigationPolicy(policy, gctx,
surfaceLookupFn, logger());
});
if (detrayGrid.has_value()) {
ACTS_DEBUG("Volume " << volume.volumeName()
<< " (detray idx: " << volPayload.index.link
<< ") has navigation policy which produced "
<< detrayGrid->bins.size() << " populated bins");
detrayGrid->owner_link.link = volPayload.index.link;
// Add the surface grid to the payload
surfaceGrids.grids[volPayload.index.link].push_back(*detrayGrid);
}
// per volume, we have a VECTOR of grids: what are they? are they always
// tied to a surface? which one?
}
});
// HACK: Beampipe MUST have index 0
std::size_t beampipeIdx = volumeIds.at(m_cfg.beampipeVolume);
ACTS_DEBUG("Beampipe volume (" << m_cfg.beampipeVolume->volumeName()
<< ") index: " << beampipeIdx);
ACTS_DEBUG("Volume at index 0 is " << detPayload.volumes.at(0).name);
// Swap beampipe and world volumes self-index
std::swap(detPayload.volumes.at(0).index.link,
detPayload.volumes.at(beampipeIdx).index.link);
// Swap beampipe and world volumes location in vector
std::swap(detPayload.volumes.at(0), detPayload.volumes.at(beampipeIdx));
// Adjust volume indices in surfaces after swapping
for (auto& vol : detPayload.volumes) {
for (auto& srf : vol.surfaces) {
auto& mask = srf.masks.at(0);
if (mask.volume_link.link == beampipeIdx) {
mask.volume_link.link = 0;
} else if (mask.volume_link.link == 0) {
mask.volume_link.link = beampipeIdx;
}
}
}
{
// Possibly swap homogeneous material entries in vector if they both exist
auto find = [](std::size_t id) {
return [id](const auto& vol) { return vol.volume_link.link == id; };
};
auto beampipeIt =
std::ranges::find_if(dthmPayload.volumes, find(beampipeIdx));
auto worldIt = std::ranges::find_if(dthmPayload.volumes, find(0));
if (beampipeIt != dthmPayload.volumes.end() &&
worldIt != dthmPayload.volumes.end()) {
// BOTH world and beampipe have homogoenous material: swap them
ACTS_DEBUG("Swapping beampipe and world homogoenous material entries");
std::swap(*beampipeIt, *worldIt);
}
// Retarget the entries, regardless of whether there is an entry for only
// one of them
for (auto& mat : dthmPayload.volumes) {
if (mat.volume_link.link == beampipeIdx) {
ACTS_DEBUG("Reassigning beampipe homogoenous material to index 0");
mat.volume_link.link = 0;
} else if (mat.volume_link.link == 0) {
ACTS_DEBUG("Reassigning world homogoenous material to beampipe index "
<< beampipeIdx);
mat.volume_link.link = beampipeIdx;
}
}
}
{
// Adjust material grids after swapping
auto beampipeGridIt = materialGrids.grids.find(beampipeIdx);
auto worldGridIt = materialGrids.grids.find(0);
if (beampipeGridIt != materialGrids.grids.end() &&
worldGridIt != materialGrids.grids.end()) {
// BOTH world and beampipe have grid specifiers: swap them
ACTS_DEBUG("Swapping beampipe and world material grid specifiers");
std::swap(beampipeGridIt->second, worldGridIt->second);
} else if (beampipeGridIt != materialGrids.grids.end()) {
// ONLY beampipe has grid specifier: move it to world
ACTS_DEBUG("Moving beampipe material grid specifier to world");
materialGrids.grids[0] = std::move(beampipeGridIt->second);
materialGrids.grids.erase(beampipeGridIt);
} else if (worldGridIt != materialGrids.grids.end()) {
// ONLY world has grid specifier: move it to beampipe
ACTS_DEBUG("Moving world material grid specifier to beampipe");
materialGrids.grids[beampipeIdx] = std::move(worldGridIt->second);
materialGrids.grids.erase(worldGridIt);
}
}
{
// Adjust surface grids after swapping
// @NOTE: The beampipe should **generally** not have a surface grid, but
// let's be safe and swap them regardless
auto beampipeGridIt = surfaceGrids.grids.find(beampipeIdx);
auto worldGridIt = surfaceGrids.grids.find(0);