File indexing completed on 2025-07-08 08:10:49
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Geometry/Blueprint.hpp"
0010
0011 #include "Acts/Definitions/Units.hpp"
0012 #include "Acts/Geometry/BlueprintNode.hpp"
0013 #include "Acts/Geometry/ContainerBlueprintNode.hpp"
0014 #include "Acts/Geometry/CylinderVolumeStack.hpp"
0015 #include "Acts/Geometry/GeometryIdentifierBlueprintNode.hpp"
0016 #include "Acts/Geometry/LayerBlueprintNode.hpp"
0017 #include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp"
0018 #include "Acts/Geometry/StaticBlueprintNode.hpp"
0019 #include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
0020 #include "Acts/Geometry/VolumeResizeStrategy.hpp"
0021 #include "Acts/Navigation/NavigationStream.hpp"
0022 #include "Acts/Plugins/Python/Utilities.hpp"
0023 #include "Acts/Utilities/AxisDefinitions.hpp"
0024 #include "Acts/Utilities/Logger.hpp"
0025
0026 #include <fstream>
0027 #include <random>
0028 #include <stdexcept>
0029 #include <utility>
0030
0031 #include <pybind11/functional.h>
0032 #include <pybind11/pybind11.h>
0033 #include <pybind11/pytypes.h>
0034 #include <pybind11/stl.h>
0035 #include <pybind11/stl/filesystem.h>
0036
0037 namespace py = pybind11;
0038 using namespace pybind11::literals;
0039
0040 namespace Acts::Python {
0041 namespace {
0042 using std::uniform_real_distribution;
0043
0044
0045 void pseudoNavigation(const TrackingGeometry& trackingGeometry,
0046 const GeometryContext& gctx, std::filesystem::path& path,
0047 std::size_t runs, std::size_t substepsPerCm,
0048 std::pair<double, double> etaRange,
0049 Logging::Level logLevel) {
0050 using namespace Acts::UnitLiterals;
0051
0052 ACTS_LOCAL_LOGGER(getDefaultLogger("pseudoNavigation", logLevel));
0053
0054 std::ofstream csv{path};
0055 csv << "x,y,z,volume,boundary,sensitive,material" << std::endl;
0056
0057 std::mt19937 rnd{42};
0058
0059 std::uniform_real_distribution<> dist{-1, 1};
0060 std::uniform_real_distribution<> subStepDist{0.01, 0.99};
0061
0062 double thetaMin = 2 * std::atan(std::exp(-etaRange.first));
0063 double thetaMax = 2 * std::atan(std::exp(-etaRange.second));
0064 std::uniform_real_distribution<> thetaDist{thetaMin, thetaMax};
0065
0066 using namespace Acts::UnitLiterals;
0067
0068 for (std::size_t run = 0; run < runs; run++) {
0069 Vector3 position = Vector3::Zero();
0070
0071 double theta = thetaDist(rnd);
0072 double phi = 2 * std::numbers::pi * dist(rnd);
0073
0074 Vector3 direction;
0075 direction[0] = std::sin(theta) * std::cos(phi);
0076 direction[1] = std::sin(theta) * std::sin(phi);
0077 direction[2] = std::cos(theta);
0078
0079 ACTS_VERBOSE("start navigation " << run);
0080 ACTS_VERBOSE("pos: " << position.transpose());
0081 ACTS_VERBOSE("dir: " << direction.transpose());
0082 ACTS_VERBOSE(direction.norm());
0083
0084 std::mt19937 rng{static_cast<unsigned int>(run)};
0085
0086 const auto* volume = trackingGeometry.lowestTrackingVolume(gctx, position);
0087 assert(volume != nullptr);
0088 ACTS_VERBOSE(volume->volumeName());
0089
0090 NavigationStream main;
0091 const TrackingVolume* currentVolume = volume;
0092
0093 csv << run << "," << position[0] << "," << position[1] << ","
0094 << position[2];
0095 csv << "," << volume->geometryId().volume();
0096 csv << "," << volume->geometryId().boundary();
0097 csv << "," << volume->geometryId().sensitive();
0098 csv << "," << 0;
0099 csv << std::endl;
0100
0101 ACTS_VERBOSE("start pseudo navigation");
0102
0103 auto writeIntersection = [&](const Vector3& pos, const Surface& surface) {
0104 csv << run << "," << pos[0] << "," << pos[1] << "," << pos[2];
0105 csv << "," << surface.geometryId().volume();
0106 csv << "," << surface.geometryId().boundary();
0107 csv << "," << surface.geometryId().sensitive();
0108 csv << "," << (surface.surfaceMaterial() != nullptr ? 1 : 0);
0109 csv << std::endl;
0110 };
0111
0112 for (std::size_t i = 0; i < 100; i++) {
0113 assert(currentVolume != nullptr);
0114 main = NavigationStream{};
0115
0116 AppendOnlyNavigationStream navStream{main};
0117 currentVolume->initializeNavigationCandidates(
0118 {.position = position, .direction = direction}, navStream, logger());
0119
0120 ACTS_VERBOSE(main.candidates().size() << " candidates");
0121
0122 for (const auto& candidate : main.candidates()) {
0123 ACTS_VERBOSE(" -> " << candidate.surface().geometryId());
0124 ACTS_VERBOSE(" " << candidate.surface().toStream(gctx));
0125 }
0126
0127 ACTS_VERBOSE("initializing candidates");
0128 main.initialize(gctx, {position, direction}, BoundaryTolerance::None());
0129
0130 ACTS_VERBOSE(main.candidates().size() << " candidates remaining");
0131
0132 for (const auto& candidate : main.candidates()) {
0133 ACTS_VERBOSE(" -> " << candidate.surface().geometryId());
0134 ACTS_VERBOSE(" " << candidate.surface().toStream(gctx));
0135 }
0136
0137 if (main.currentCandidate().surface().isOnSurface(gctx, position,
0138 direction)) {
0139 ACTS_VERBOSE(
0140 "Already on surface at initialization, skipping candidate");
0141
0142 writeIntersection(position, main.currentCandidate().surface());
0143
0144 if (!main.switchToNextCandidate()) {
0145 ACTS_WARNING("candidates exhausted unexpectedly");
0146 break;
0147 }
0148 }
0149
0150 bool terminated = false;
0151 while (main.remainingCandidates() > 0) {
0152 const auto& candidate = main.currentCandidate();
0153
0154 ACTS_VERBOSE(candidate.portal);
0155 ACTS_VERBOSE(candidate.intersection.position().transpose());
0156
0157 ACTS_VERBOSE("moving to position: " << position.transpose() << " (r="
0158 << VectorHelpers::perp(position)
0159 << ")");
0160
0161 Vector3 delta = candidate.intersection.position() - position;
0162
0163 std::size_t substeps =
0164 std::max(1l, std::lround(delta.norm() / 10_cm * substepsPerCm));
0165
0166 for (std::size_t j = 0; j < substeps; j++) {
0167 Vector3 subpos = position + subStepDist(rng) * delta;
0168 csv << run << "," << subpos[0] << "," << subpos[1] << ","
0169 << subpos[2];
0170 csv << "," << currentVolume->geometryId().volume();
0171 csv << ",0,0,0";
0172 csv << std::endl;
0173 }
0174
0175 position = candidate.intersection.position();
0176 ACTS_VERBOSE(" -> "
0177 << position.transpose()
0178 << " (r=" << VectorHelpers::perp(position) << ")");
0179
0180 writeIntersection(position, candidate.surface());
0181
0182 if (candidate.portal != nullptr) {
0183 ACTS_VERBOSE(
0184 "On portal: " << candidate.portal->surface().toStream(gctx));
0185 currentVolume =
0186 candidate.portal->resolveVolume(gctx, position, direction)
0187 .value();
0188
0189 if (currentVolume == nullptr) {
0190 ACTS_VERBOSE("switched to nullptr -> we're done");
0191 terminated = true;
0192 }
0193 break;
0194
0195 } else {
0196 ACTS_VERBOSE("Not on portal");
0197 }
0198
0199 main.switchToNextCandidate();
0200 }
0201
0202 if (terminated) {
0203 ACTS_VERBOSE("Terminate pseudo navigation");
0204 break;
0205 }
0206
0207 ACTS_VERBOSE("switched to " << currentVolume->volumeName());
0208
0209 ACTS_VERBOSE("-----");
0210 }
0211 }
0212 }
0213
0214 }
0215
0216 void addBlueprint(Context& ctx) {
0217 using Acts::Experimental::Blueprint;
0218 using Acts::Experimental::BlueprintNode;
0219 using Acts::Experimental::BlueprintOptions;
0220 using Acts::Experimental::CuboidContainerBlueprintNode;
0221 using Acts::Experimental::CylinderContainerBlueprintNode;
0222 using Acts::Experimental::GeometryIdentifierBlueprintNode;
0223 using Acts::Experimental::LayerBlueprintNode;
0224 using Acts::Experimental::MaterialDesignatorBlueprintNode;
0225 using Acts::Experimental::StaticBlueprintNode;
0226
0227 auto m = ctx.get("main");
0228
0229 auto blueprintNode =
0230 py::class_<BlueprintNode, std::shared_ptr<BlueprintNode>>(
0231 m, "BlueprintNode");
0232
0233 auto rootNode =
0234 py::class_<Blueprint, BlueprintNode, std::shared_ptr<Blueprint>>(
0235 m, "Blueprint");
0236
0237 rootNode
0238 .def(py::init<const Blueprint::Config&>())
0239
0240
0241 .def(
0242 "construct",
0243 [](Blueprint& self, const BlueprintOptions& options,
0244 const GeometryContext& gctx,
0245 Logging::Level level) -> std::shared_ptr<TrackingGeometry> {
0246 return self.construct(options, gctx,
0247 *getDefaultLogger("Blueprint", level));
0248 },
0249 py::arg("options"), py::arg("gctx"),
0250 py::arg("level") = Logging::INFO);
0251
0252 {
0253 auto c = py::class_<Blueprint::Config>(rootNode, "Config").def(py::init());
0254 ACTS_PYTHON_STRUCT(c, envelope);
0255 }
0256
0257 auto addContextManagerProtocol = []<typename class_>(class_& cls) {
0258 using type = typename class_::type;
0259 cls.def("__enter__", [](type& self) -> type& { return self; })
0260 .def("__exit__", [](type& , const py::object& ,
0261 const py::object& ,
0262 const py::object& ) {
0263
0264 });
0265 };
0266
0267 auto addNodeMethods = [&blueprintNode](
0268 std::initializer_list<std::string> names,
0269 auto&& callable, auto&&... args) {
0270 for (const auto& name : names) {
0271 blueprintNode.def(name.c_str(), callable, args...);
0272 }
0273 };
0274
0275 blueprintNode
0276 .def("__str__",
0277 [](const BlueprintNode& self) {
0278 std::stringstream ss;
0279 ss << self;
0280 return ss.str();
0281 })
0282 .def("addChild", &BlueprintNode::addChild)
0283 .def_property_readonly("children",
0284 py::overload_cast<>(&BlueprintNode::children))
0285 .def("clearChildren", &BlueprintNode::clearChildren)
0286 .def_property_readonly("name", &BlueprintNode::name)
0287 .def_property_readonly("depth", &BlueprintNode::depth)
0288 .def("graphviz", [](BlueprintNode& self, const py::object& fh) {
0289 std::stringstream ss;
0290 self.graphviz(ss);
0291 fh.attr("write")(ss.str());
0292 });
0293
0294 py::class_<BlueprintOptions>(m, "BlueprintOptions")
0295 .def(py::init<>())
0296 .def_readwrite("defaultNavigationPolicyFactory",
0297 &BlueprintOptions::defaultNavigationPolicyFactory);
0298
0299 py::class_<BlueprintNode::MutableChildRange>(blueprintNode,
0300 "MutableChildRange")
0301 .def(
0302 "__iter__",
0303 [](BlueprintNode::MutableChildRange& self) {
0304 return py::make_iterator(self.begin(), self.end());
0305 },
0306 py::keep_alive<0, 1>())
0307 .def(
0308 "__getitem__",
0309 [](BlueprintNode::MutableChildRange& self, int i) -> BlueprintNode& {
0310 if (i < 0) {
0311 i += self.size();
0312 }
0313 return self.at(i);
0314 },
0315 py::return_value_policy::reference_internal)
0316 .def("__len__", [](const BlueprintNode::MutableChildRange& self) {
0317 return self.size();
0318 });
0319
0320 auto staticNode =
0321 py::class_<StaticBlueprintNode, BlueprintNode,
0322 std::shared_ptr<StaticBlueprintNode>>(m, "StaticBlueprintNode")
0323 .def(py::init([](const Transform3& transform,
0324 const std::shared_ptr<VolumeBounds>& bounds,
0325 const std::string& name) {
0326 return std::make_shared<StaticBlueprintNode>(
0327 std::make_unique<Acts::TrackingVolume>(transform, bounds,
0328 name));
0329 }),
0330 py::arg("transform"), py::arg("bounds"),
0331 py::arg("name") = "undefined")
0332 .def_property("navigationPolicyFactory",
0333 &StaticBlueprintNode::navigationPolicyFactory,
0334 &StaticBlueprintNode::setNavigationPolicyFactory);
0335
0336 addContextManagerProtocol(staticNode);
0337
0338 addNodeMethods(
0339 {"StaticVolume", "addStaticVolume"},
0340 [](BlueprintNode& self, const Transform3& transform,
0341 const std::shared_ptr<VolumeBounds>& bounds, const std::string& name) {
0342 auto node = std::make_shared<StaticBlueprintNode>(
0343 std::make_unique<TrackingVolume>(transform, bounds, name));
0344 self.addChild(node);
0345 return node;
0346 },
0347 py::arg("transform"), py::arg("bounds"), py::arg("name") = "undefined");
0348
0349 auto cylNode =
0350 py::class_<CylinderContainerBlueprintNode, BlueprintNode,
0351 std::shared_ptr<CylinderContainerBlueprintNode>>(
0352 m, "CylinderContainerBlueprintNode")
0353 .def(py::init<const std::string&, AxisDirection,
0354 VolumeAttachmentStrategy, VolumeResizeStrategy>(),
0355 py::arg("name"), py::arg("direction"),
0356 py::arg("attachmentStrategy") = VolumeAttachmentStrategy::Gap,
0357 py::arg("resizeStrategy") = VolumeResizeStrategy::Gap)
0358 .def_property("attachmentStrategy",
0359 &CylinderContainerBlueprintNode::attachmentStrategy,
0360 &CylinderContainerBlueprintNode::setAttachmentStrategy)
0361 .def_property("resizeStrategies",
0362 &CylinderContainerBlueprintNode::resizeStrategies,
0363 [](CylinderContainerBlueprintNode& self,
0364 std::pair<VolumeResizeStrategy, VolumeResizeStrategy>
0365 strategies) {
0366 self.setResizeStrategies(strategies.first,
0367 strategies.second);
0368 })
0369 .def_property("direction", &CylinderContainerBlueprintNode::direction,
0370 &CylinderContainerBlueprintNode::setDirection);
0371
0372 addContextManagerProtocol(cylNode);
0373
0374 addNodeMethods(
0375 {"CylinderContainer", "addCylinderContainer"},
0376 [](BlueprintNode& self, const std::string& name,
0377 AxisDirection direction) {
0378 auto cylinder =
0379 std::make_shared<CylinderContainerBlueprintNode>(name, direction);
0380 self.addChild(cylinder);
0381 return cylinder;
0382 },
0383 py::arg("name"), py::arg("direction"));
0384
0385 auto boxNode =
0386 py::class_<CuboidContainerBlueprintNode, BlueprintNode,
0387 std::shared_ptr<CuboidContainerBlueprintNode>>(
0388 m, "CuboidContainerBlueprintNode")
0389 .def(py::init<const std::string&, AxisDirection,
0390 VolumeAttachmentStrategy, VolumeResizeStrategy>(),
0391 py::arg("name"), py::arg("direction"),
0392 py::arg("attachmentStrategy") = VolumeAttachmentStrategy::Gap,
0393 py::arg("resizeStrategy") = VolumeResizeStrategy::Gap)
0394 .def_property("attachmentStrategy",
0395 &CuboidContainerBlueprintNode::attachmentStrategy,
0396 &CuboidContainerBlueprintNode::setAttachmentStrategy)
0397 .def_property("resizeStrategies",
0398 &CuboidContainerBlueprintNode::resizeStrategies,
0399 &CuboidContainerBlueprintNode::setResizeStrategies)
0400 .def_property("direction", &CuboidContainerBlueprintNode::direction,
0401 &CuboidContainerBlueprintNode::setDirection);
0402
0403 addContextManagerProtocol(boxNode);
0404
0405 addNodeMethods(
0406 {"CuboidContainer", "addCuboidContainer"},
0407 [](BlueprintNode& self, const std::string& name,
0408 AxisDirection direction) {
0409 auto cylinder =
0410 std::make_shared<CuboidContainerBlueprintNode>(name, direction);
0411 self.addChild(cylinder);
0412 return cylinder;
0413 },
0414 py::arg("name"), py::arg("direction"));
0415
0416 auto matNode = py::class_<MaterialDesignatorBlueprintNode, BlueprintNode,
0417 std::shared_ptr<MaterialDesignatorBlueprintNode>>(
0418 m, "MaterialDesignatorBlueprintNode")
0419 .def(py::init<const std::string&>(), "name"_a)
0420 .def("configureFace",
0421 py::overload_cast<CylinderVolumeBounds::Face,
0422 const DirectedProtoAxis&,
0423 const DirectedProtoAxis&>(
0424 &MaterialDesignatorBlueprintNode::configureFace),
0425 "face"_a, "loc0"_a, "loc1"_a)
0426 .def("configureFace",
0427 py::overload_cast<CuboidVolumeBounds::Face,
0428 const DirectedProtoAxis&,
0429 const DirectedProtoAxis&>(
0430 &MaterialDesignatorBlueprintNode::configureFace),
0431 "face"_a, "loc0"_a, "loc1"_a);
0432
0433 addContextManagerProtocol(matNode);
0434
0435 addNodeMethods(
0436 {"Material", "addMaterial"},
0437 [](BlueprintNode& self, const std::string& name) {
0438 auto child = std::make_shared<MaterialDesignatorBlueprintNode>(name);
0439 self.addChild(child);
0440 return child;
0441 },
0442 "name"_a);
0443
0444 auto layerNode =
0445 py::class_<LayerBlueprintNode, StaticBlueprintNode,
0446 std::shared_ptr<LayerBlueprintNode>>(m, "LayerBlueprintNode")
0447 .def(py::init<const std::string&>(), py::arg("name"))
0448 .def_property_readonly("name", &LayerBlueprintNode::name)
0449 .def_property("surfaces", &LayerBlueprintNode::surfaces,
0450 &LayerBlueprintNode::setSurfaces)
0451 .def_property("transform", &LayerBlueprintNode::transform,
0452 &LayerBlueprintNode::setTransform)
0453 .def_property("envelope", &LayerBlueprintNode::envelope,
0454 &LayerBlueprintNode::setEnvelope)
0455 .def_property("layerType", &LayerBlueprintNode::layerType,
0456 &LayerBlueprintNode::setLayerType)
0457 .def_property("navigationPolicyFactory",
0458 &LayerBlueprintNode::navigationPolicyFactory,
0459 &LayerBlueprintNode::setNavigationPolicyFactory);
0460
0461 py::enum_<LayerBlueprintNode::LayerType>(layerNode, "LayerType")
0462 .value("Cylinder", LayerBlueprintNode::LayerType::Cylinder)
0463 .value("Disc", LayerBlueprintNode::LayerType::Disc)
0464 .value("Plane", LayerBlueprintNode::LayerType::Plane);
0465
0466 addContextManagerProtocol(layerNode);
0467
0468 addNodeMethods(
0469 {"Layer", "addLayer"},
0470 [](BlueprintNode& self, const std::string& name) {
0471 auto child = std::make_shared<LayerBlueprintNode>(name);
0472 self.addChild(child);
0473 return child;
0474 },
0475 py::arg("name"));
0476
0477 auto geoIdNode =
0478 py::class_<GeometryIdentifierBlueprintNode, BlueprintNode,
0479 std::shared_ptr<GeometryIdentifierBlueprintNode>>(
0480 m, "GeometryIdentifierBlueprintNode")
0481 .def(py::init<>())
0482 .def("setLayerIdTo", &GeometryIdentifierBlueprintNode::setLayerIdTo,
0483 py::arg("value"))
0484 .def("incrementLayerIds",
0485 &GeometryIdentifierBlueprintNode::incrementLayerIds,
0486 py::arg("start") = 0)
0487 .def("setAllVolumeIdsTo",
0488 &GeometryIdentifierBlueprintNode::setAllVolumeIdsTo,
0489 py::arg("value"))
0490
0491 .def(
0492 "sortBy",
0493 [](GeometryIdentifierBlueprintNode& self,
0494 const py::function& func) -> GeometryIdentifierBlueprintNode& {
0495 if (func.is_none()) {
0496 throw std::invalid_argument(
0497 "sortBy requires a comparison function");
0498 }
0499 return self.sortBy(
0500 [func](const TrackingVolume& a, const TrackingVolume& b) {
0501 return func(&a, &b).cast<bool>();
0502 });
0503 },
0504 py::arg("compare"));
0505
0506 auto geoIdFactory = [](BlueprintNode& self) {
0507 auto child = std::make_shared<GeometryIdentifierBlueprintNode>();
0508 self.addChild(child);
0509 return child;
0510 };
0511
0512 addNodeMethods({"GeometryIdentifier", "withGeometryIdentifier"},
0513 geoIdFactory);
0514 addContextManagerProtocol(geoIdNode);
0515
0516
0517 m.def("pseudoNavigation", &pseudoNavigation, "trackingGeometry"_a, "gctx"_a,
0518 "path"_a, "runs"_a, "substepsPerCm"_a = 2,
0519 "etaRange"_a = std::pair{-4.5, 4.5}, "logLevel"_a = Logging::INFO);
0520 }
0521
0522 }