From c0a69772c3ba3d9d650c2dc5f3b276cf55d462dc Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 24 Oct 2024 13:25:20 +0200 Subject: [PATCH] feat(nav): Gen3 Navigation policies and factory (#3760) Terminology: - "Navigation delegate": the function that is registered with a tracking volume. In principle, this can be anything - "Navigation policy": the object that is registered with the tracking volume. It contains a method that is connected to the "navigation delegate". It has extra methods - "Navigation policy factory": To delay construction of the actual policy object **until after** the volume is fully sized and has all of its internal structure registered, the blueprint tree only contains *navigation policy factories*. This is configurable. During construction, the factory is applied to volumes, and produces a policy that is registered with the volume. This is called "navigation policy factory" from a conceptual point of view. - "MultiNavigationPolicy": chains together multiple policies in a sort of composition. You can have one policy that only deals with portals, one for sensitive and one for passive surfaces, for example. - To make this less annoying to construct, as you would have to manage the factory, I'm adding a concrete class `NavigationPolicyFactory`. Its job is to make defining a factory that produces a "MultiNavigationPolicy" easy, like: ```cpp using namespace Acts; using SurfaceArrayNavigationPolicy::LayerType; SurfaceArrayNavigationPolicy::Config config{ .layerType = LayerType::Cylinder, .bins = {10, 10} }; auto factory = NavigationPolicyFactory::make() .add() .add(config); ``` or in python: ```python policy = ( acts.NavigationPolicyFactory.make() .add(acts.TryAllPortalNavigationPolicy) .add(acts.TryAllSurfaceNavigationPolicy) .add( acts.SurfaceArrayNavigationPolicy, acts.SurfaceArrayNavigationPolicy.Config( layerType=acts.SurfaceArrayNavigationPolicy.LayerType.Plane, bins=(10, 10), ), ) ) ``` Part of #3502 --- .../Acts/Geometry/NavigationPolicyFactory.hpp | 220 ++++++++++++++++ Core/include/Acts/Geometry/TrackingVolume.hpp | 32 ++- .../Acts/Navigation/INavigationPolicy.hpp | 73 ++++++ .../Acts/Navigation/MultiNavigationPolicy.hpp | 78 ++++++ .../Acts/Navigation/NavigationDelegate.hpp | 35 +++ .../Acts/Navigation/NavigationStream.hpp | 24 +- .../SurfaceArrayNavigationPolicy.hpp | 83 ++++++ .../Navigation/TryAllNavigationPolicy.hpp | 63 +++++ Core/src/Geometry/TrackingVolume.cpp | 26 ++ Core/src/Navigation/CMakeLists.txt | 8 +- Core/src/Navigation/NavigationStream.cpp | 102 +++++--- .../SurfaceArrayNavigationPolicy.cpp | 83 ++++++ .../src/Navigation/TryAllNavigationPolicy.cpp | 54 ++++ Examples/Python/CMakeLists.txt | 1 + Examples/Python/src/ModuleEntry.cpp | 2 + Examples/Python/src/Navigation.cpp | 203 +++++++++++++++ Examples/Python/tests/test_navigation.py | 42 +++ .../UnitTests/Core/Navigation/CMakeLists.txt | 1 + .../Core/Navigation/NavigationPolicyTests.cpp | 240 ++++++++++++++++++ .../Core/Navigation/NavigationStreamTests.cpp | 9 +- 20 files changed, 1324 insertions(+), 55 deletions(-) create mode 100644 Core/include/Acts/Geometry/NavigationPolicyFactory.hpp create mode 100644 Core/include/Acts/Navigation/INavigationPolicy.hpp create mode 100644 Core/include/Acts/Navigation/MultiNavigationPolicy.hpp create mode 100644 Core/include/Acts/Navigation/NavigationDelegate.hpp create mode 100644 Core/include/Acts/Navigation/SurfaceArrayNavigationPolicy.hpp create mode 100644 Core/include/Acts/Navigation/TryAllNavigationPolicy.hpp create mode 100644 Core/src/Navigation/SurfaceArrayNavigationPolicy.cpp create mode 100644 Core/src/Navigation/TryAllNavigationPolicy.cpp create mode 100644 Examples/Python/src/Navigation.cpp create mode 100644 Examples/Python/tests/test_navigation.py create mode 100644 Tests/UnitTests/Core/Navigation/NavigationPolicyTests.cpp diff --git a/Core/include/Acts/Geometry/NavigationPolicyFactory.hpp b/Core/include/Acts/Geometry/NavigationPolicyFactory.hpp new file mode 100644 index 00000000000..4d9542af826 --- /dev/null +++ b/Core/include/Acts/Geometry/NavigationPolicyFactory.hpp @@ -0,0 +1,220 @@ +// 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/. + +#pragma once + +#include "Acts/Navigation/INavigationPolicy.hpp" +#include "Acts/Navigation/MultiNavigationPolicy.hpp" + +#include +#include +namespace Acts { + +class TrackingVolume; +class GeometryContext; +class Logger; +class INavigationPolicy; + +namespace detail { +template +class NavigationPolicyFactoryImpl; +} + +/// Base class for navigation policy factories. The factory can be assembled +/// iteratively by using `make` followed by a number of calls to the `add` +/// function of the helper type. Example: +/// +/// ```cpp +/// auto factory = NavigationPolicyFactory::make() +/// .add(arg1, arg2) +/// .add(/*no args*/) +/// .asUniquePtr(); +/// ``` +class NavigationPolicyFactory { + public: + virtual ~NavigationPolicyFactory() = default; + + // This needs to be listed here, but the return type cannot be spelled out + // yet. + static auto make(); + + // This will potentially get serialization interface and deserialization + // functionality + + virtual std::unique_ptr build( + const GeometryContext& gctx, const TrackingVolume& volume, + const Logger& logger) const = 0; +}; + +namespace detail { + +template +concept NavigationPolicyIsolatedFactoryConcept = requires(F f) { + { + f(std::declval(), + std::declval(), std::declval(), + std::declval()...) + } -> std::derived_from; + + requires NavigationPolicyConcept(), + std::declval(), std::declval(), + std::declval()...))>; + + requires(std::is_copy_constructible_v && ...); +}; + +template <> +class NavigationPolicyFactoryImpl<> { + public: + template + friend class NavigationPolicyFactoryImpl; + NavigationPolicyFactoryImpl() = default; + + /// Create a factory with the specified policy added + /// @tparam P The policy type to add + /// @param args The arguments to pass to the policy constructor + /// @note Arguments need to be copy constructible because the factory must be + /// able to execute multiple times. + /// @return A new policy factory including the @c P policy. + template + requires(std::is_constructible_v && + (std::is_copy_constructible_v && ...)) + constexpr auto add(Args&&... args) && { + auto factory = [=](const GeometryContext& gctx, + const TrackingVolume& volume, const Logger& logger) { + return P{gctx, volume, logger, args...}; + }; + + return NavigationPolicyFactoryImpl{ + std::make_tuple(std::move(factory))}; + } + + /// Create a factory with a policy returned by a factory function + /// @tparam Fn The type of the function to construct the policy + /// @param args The arguments to pass to the policy factory + /// @note Arguments need to be copy constructible because the factory must be + /// able to execute multiple times. + /// @return A new policy factory including the function + template + requires(NavigationPolicyIsolatedFactoryConcept) + constexpr auto add(Fn&& fn, Args&&... args) { + auto factory = [=](const GeometryContext& gctx, + const TrackingVolume& volume, const Logger& logger) { + return fn(gctx, volume, logger, args...); + }; + + return NavigationPolicyFactoryImpl{ + std::make_tuple(std::move(factory))}; + } +}; + +template +class NavigationPolicyFactoryImpl : public NavigationPolicyFactory { + public: + /// Create a factory with the specified policy added + /// @tparam P The policy type to add + /// @param args The arguments to pass to the policy constructor + /// @note Arguments need to be copy constructible because the factory must be + /// able to execute multiple times. + /// @return A new policy factory including the @c P policy. + template + requires(std::is_constructible_v && + (std::is_copy_constructible_v && ...)) + constexpr auto add(Args&&... args) && { + auto factory = [=](const GeometryContext& gctx, + const TrackingVolume& volume, const Logger& logger) { + return P{gctx, volume, logger, args...}; + }; + + return NavigationPolicyFactoryImpl{ + std::tuple_cat(std::move(m_factories), + std::make_tuple(std::move(factory)))}; + } + + /// Create a factory with a policy returned by a factory function + /// @tparam Fn The type of the function to construct the policy + /// @param args The arguments to pass to the policy factory + /// @note Arguments need to be copy constructible because the factory must be + /// able to execute multiple times. + /// @return A new policy factory including the function + template + requires(NavigationPolicyIsolatedFactoryConcept) + constexpr auto add(Fn&& fn, Args&&... args) && { + auto factory = [=](const GeometryContext& gctx, + const TrackingVolume& volume, const Logger& logger) { + return fn(gctx, volume, logger, args...); + }; + + return NavigationPolicyFactoryImpl{ + std::tuple_cat(std::move(m_factories), + std::make_tuple(std::move(factory)))}; + } + + /// Move the factory into a unique pointer + /// @note Only callable on rvalue references + /// @return A unique pointer to the factory + constexpr std::unique_ptr> + asUniquePtr() && { + return std::make_unique>( + std::move(*this)); + } + + /// Construct a navigation policy using the factories + /// @param gctx The geometry context + /// @param volume The tracking volume + /// @param logger The logger + auto operator()(const GeometryContext& gctx, const TrackingVolume& volume, + const Logger& logger) const { + return std::apply( + [&](auto&&... factories) { + // Deduce policy type explicitly here... + using policy_type = decltype(MultiNavigationPolicy{ + std::invoke(factories, std::declval(), + std::declval(), + std::declval())...}); + + // ... so we can create a unique_ptr of the concrete type here rather + // than the base. (`make_unique` can't do type deduction) + return std::make_unique( + std::invoke(factories, gctx, volume, logger)...); + }, + m_factories); + } + + /// Construct a navigation policy using the factories + /// @param gctx The geometry context + /// @param volume The tracking volume + /// @param logger The logger + std::unique_ptr build( + const GeometryContext& gctx, const TrackingVolume& volume, + const Logger& logger) const override { + return operator()(gctx, volume, logger); + } + + private: + template + friend class NavigationPolicyFactoryImpl; + + NavigationPolicyFactoryImpl(std::tuple&& factories) + : m_factories(std::move(factories)) {} + + std::tuple m_factories; +}; + +} // namespace detail + +inline auto NavigationPolicyFactory::make() { + return detail::NavigationPolicyFactoryImpl<>{}; +} + +} // namespace Acts diff --git a/Core/include/Acts/Geometry/TrackingVolume.hpp b/Core/include/Acts/Geometry/TrackingVolume.hpp index c980314fa6d..f47f8b440fc 100644 --- a/Core/include/Acts/Geometry/TrackingVolume.hpp +++ b/Core/include/Acts/Geometry/TrackingVolume.hpp @@ -13,13 +13,13 @@ #include "Acts/Geometry/BoundarySurfaceT.hpp" #include "Acts/Geometry/GeometryContext.hpp" #include "Acts/Geometry/GeometryIdentifier.hpp" -#include "Acts/Geometry/GlueVolumesDescriptor.hpp" #include "Acts/Geometry/Layer.hpp" #include "Acts/Geometry/Portal.hpp" #include "Acts/Geometry/TrackingVolumeVisitorConcept.hpp" #include "Acts/Geometry/Volume.hpp" #include "Acts/Material/IVolumeMaterial.hpp" -#include "Acts/Surfaces/BoundaryTolerance.hpp" +#include "Acts/Navigation/NavigationDelegate.hpp" +#include "Acts/Navigation/NavigationStream.hpp" #include "Acts/Surfaces/Surface.hpp" #include "Acts/Surfaces/SurfaceArray.hpp" #include "Acts/Surfaces/SurfaceVisitorConcept.hpp" @@ -35,7 +35,7 @@ #include #include -#include +#include namespace Acts { @@ -43,7 +43,6 @@ class GlueVolumesDescriptor; class VolumeBounds; template struct NavigationOptions; -class GeometryIdentifier; class IMaterialDecorator; class ISurfaceMaterial; class IVolumeMaterial; @@ -51,6 +50,7 @@ class Surface; class TrackingVolume; struct GeometryIdentifierHook; class Portal; +class INavigationPolicy; /// Interface types of the Gen1 geometry model /// @note This interface is being replaced, and is subject to removal @@ -117,8 +117,8 @@ class TrackingVolume : public Volume { ~TrackingVolume() override; TrackingVolume(const TrackingVolume&) = delete; TrackingVolume& operator=(const TrackingVolume&) = delete; - TrackingVolume(TrackingVolume&&) = default; - TrackingVolume& operator=(TrackingVolume&&) = default; + TrackingVolume(TrackingVolume&&); + TrackingVolume& operator=(TrackingVolume&&); /// Constructor for a container Volume /// - vacuum filled volume either as a for other tracking volumes @@ -155,7 +155,6 @@ class TrackingVolume : public Volume { /// @param volumeName is a string identifier TrackingVolume(Volume& volume, const std::string& volumeName = "undefined"); - // @TODO: This needs to be refactored to include Gen3 volumes /// Return the associated sub Volume, returns THIS if no subVolume exists /// @param gctx The current geometry context object, e.g. alignment /// @param position is the global position associated with that search @@ -498,6 +497,21 @@ class TrackingVolume : public Volume { const ViewConfig& portalViewConfig, const ViewConfig& sensitiveViewConfig) const; + /// Register a navigation policy with this volume. The argument can not be + /// nullptr. + /// @param policy is the navigation policy to be registered + void setNavigationPolicy(std::unique_ptr policy); + + /// Populate the navigation stream with navigation candidates from this + /// volume. Internally, this consults the registered navigation policy, where + /// the default is a noop. + /// @param args are the navigation arguments + /// @param stream is the navigation stream to be updated + /// @param logger is the logger + void initializeNavigationCandidates(const NavigationArguments& args, + AppendOnlyNavigationStream& stream, + const Logger& logger) const; + private: void connectDenseBoundarySurfaces( MutableTrackingVolumeVector& confinedDenseVolumes); @@ -561,6 +575,10 @@ class TrackingVolume : public Volume { std::vector> m_volumes; std::vector> m_portals; std::vector> m_surfaces; + + std::unique_ptr m_navigationPolicy; + + NavigationDelegate m_navigationDelegate{}; }; } // namespace Acts diff --git a/Core/include/Acts/Navigation/INavigationPolicy.hpp b/Core/include/Acts/Navigation/INavigationPolicy.hpp new file mode 100644 index 00000000000..6794dbdea09 --- /dev/null +++ b/Core/include/Acts/Navigation/INavigationPolicy.hpp @@ -0,0 +1,73 @@ +// 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/. + +#pragma once + +#include "Acts/Navigation/NavigationDelegate.hpp" +#include "Acts/Navigation/NavigationStream.hpp" +#include "Acts/Utilities/DelegateChainBuilder.hpp" + +#include + +namespace Acts { + +class TrackingVolume; +class INavigationPolicy; + +/// Concept for a navigation policy +/// This exists so `updateState` can be a non-virtual method and we still have a +/// way to enforce it exists. +template +concept NavigationPolicyConcept = requires { + requires std::is_base_of_v; + // Has a conforming update method + requires requires(T policy, const NavigationArguments& args) { + policy.initializeCandidates(args, + std::declval(), + std::declval()); + }; +}; + +/// Base class for all navigation policies. The policy needs to be *connected* +/// to a delegate via a virtual method for it to become active. The update +/// method is not part of the class interface. The conventional `updateState` +/// method is only required for use with the navigation policy factory, +/// otherwise `connect` is free to connect any function. +class INavigationPolicy { + public: + /// Noop update function that is suitable as a default for default navigation + /// delegates. + static void noopInitializeCandidates(const NavigationArguments& /*unused*/, + AppendOnlyNavigationStream& /*unused*/, + const Logger& /*unused*/) {} + + /// Virtual destructor so policies can be held through this base class. + virtual ~INavigationPolicy() = default; + + /// Connect a policy with a delegate (usually a member of a volume). + /// This method exists to allow a policy to ensure a non-virtual function is + /// registered with the delegate. + /// @param delegate The delegate to connect to + virtual void connect(NavigationDelegate& delegate) const = 0; + + protected: + /// Internal helper function for derived classes that conform to the concept + /// and have a conventional `updateState` method. Mainly used to save some + /// boilerplate. + /// @tparam T The type of the navigation policy + /// @param delegate The delegate to connect to + template + void connectDefault(NavigationDelegate& delegate) const { + // This cannot be a concept because we use it in CRTP below + const auto* self = static_cast(this); + DelegateChainBuilder{delegate}.add<&T::initializeCandidates>(self).store( + delegate); + } +}; + +} // namespace Acts diff --git a/Core/include/Acts/Navigation/MultiNavigationPolicy.hpp b/Core/include/Acts/Navigation/MultiNavigationPolicy.hpp new file mode 100644 index 00000000000..c5c8a397aea --- /dev/null +++ b/Core/include/Acts/Navigation/MultiNavigationPolicy.hpp @@ -0,0 +1,78 @@ +// 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/. + +#pragma once + +#include "Acts/Navigation/INavigationPolicy.hpp" + +namespace Acts { + +/// Base class for multi navigation policies +class MultiNavigationPolicyBase : public INavigationPolicy {}; + +/// Combined navigation policy that calls all contained other navigation +/// policies. This class only works with policies complying with +/// `NavigationPolicyConcept`, which means that they have a conventional +/// `updateState` method. +/// +/// Internally, this uses a delegate chain factory to produce an unrolled +/// delegate chain. +/// +/// @tparam Policies The navigation policies to be combined +template + requires(sizeof...(Policies) > 0) +class MultiNavigationPolicy final : public MultiNavigationPolicyBase { + public: + /// Constructor from a set of child policies. + /// @param policies The child policies + MultiNavigationPolicy(Policies&&... policies) + : m_policies{std::move(policies)...} {} + + /// Implementation of the connection to a navigation delegate. + /// It uses the delegate chain factory to produce a delegate chain and stores + /// that chain in the owning navigation delegate. + /// @param delegate The navigation delegate to connect to + void connect(NavigationDelegate& delegate) const override { + auto factory = add(DelegateChainBuilder{delegate}, + std::index_sequence_for{}); + + factory.store(delegate); + } + + /// Access the contained policies + /// @return The contained policies + const std::tuple& policies() const { return m_policies; } + + private: + /// Internal helper to build the delegate chain + template + constexpr auto add( + auto factory, + std::integer_sequence /*unused*/) const { + return add(factory); + } + + /// Internal helper to build the delegate chain + template + constexpr auto add(auto factory) const { + using policy_type = std::tuple_element_t; + auto* policy = &std::get(m_policies); + + auto added = + factory.template add<&policy_type::initializeCandidates>(policy); + + if constexpr (sizeof...(Is) > 0) { + return add(added); + } else { + return added; + } + } + + std::tuple m_policies; +}; +} // namespace Acts diff --git a/Core/include/Acts/Navigation/NavigationDelegate.hpp b/Core/include/Acts/Navigation/NavigationDelegate.hpp new file mode 100644 index 00000000000..8e243818d30 --- /dev/null +++ b/Core/include/Acts/Navigation/NavigationDelegate.hpp @@ -0,0 +1,35 @@ +// 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/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Navigation/NavigationStream.hpp" +#include "Acts/Surfaces/BoundaryTolerance.hpp" +#include "Acts/Utilities/Delegate.hpp" + +namespace Acts { + +class NavigationStream; +class Logger; + +/// Struct that serves as the argument to the navigation delegate. +/// It is not supposed to be used as an lvalue. +struct NavigationArguments { + Vector3 position; + Vector3 direction; + + BoundaryTolerance tolerance = BoundaryTolerance::None(); +}; + +/// Central alias for the navigation delegate. This type is owning to support +/// (type-erased) navigation delegate chains (i.e. multiple policies). +using NavigationDelegate = OwningDelegate; + +} // namespace Acts diff --git a/Core/include/Acts/Navigation/NavigationStream.hpp b/Core/include/Acts/Navigation/NavigationStream.hpp index 71443e48915..97e154214d7 100644 --- a/Core/include/Acts/Navigation/NavigationStream.hpp +++ b/Core/include/Acts/Navigation/NavigationStream.hpp @@ -10,9 +10,11 @@ #include "Acts/Definitions/Algebra.hpp" #include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/Portal.hpp" #include "Acts/Surfaces/BoundaryTolerance.hpp" #include "Acts/Utilities/Intersection.hpp" +#include #include #include @@ -22,7 +24,6 @@ namespace Acts { namespace Experimental { class Portal; } -using namespace Experimental; class Surface; @@ -55,6 +56,7 @@ class NavigationStream { ObjectIntersection intersection = ObjectIntersection::invalid(); /// The portal + const Acts::Experimental::Portal* gen2Portal = nullptr; const Portal* portal = nullptr; /// The boundary tolerance BoundaryTolerance bTolerance = BoundaryTolerance::None(); @@ -116,25 +118,27 @@ class NavigationStream { /// /// @param surface the surface to be filled /// @param bTolerance the boundary tolerance used for the intersection - void addSurfaceCandidate(const Surface* surface, + void addSurfaceCandidate(const Surface& surface, const BoundaryTolerance& bTolerance); /// Fill n surfaces into the candidate vector /// /// @param surfaces the surfaces that are filled in /// @param bTolerance the boundary tolerance used for the intersection - void addSurfaceCandidates(const std::vector& surfaces, + void addSurfaceCandidates(std::span surfaces, const BoundaryTolerance& bTolerance); /// Fill one portal into the candidate vector /// + void addPortalCandidate(const Experimental::Portal& portal); /// @param portal the portals that are filled in - void addPortalCandidate(const Portal* portal); + + void addPortalCandidate(const Portal& portal); /// Fill n portals into the candidate vector /// /// @param portals the portals that are filled in - void addPortalCandidates(const std::vector& portals); + void addPortalCandidates(std::span portals); /// Initialize the stream from a query point /// @@ -175,4 +179,14 @@ class NavigationStream { std::size_t m_currentIndex = 0u; }; +struct AppendOnlyNavigationStream { + explicit AppendOnlyNavigationStream(NavigationStream& stream); + void addSurfaceCandidate(const Surface& surface, + const BoundaryTolerance& bTolerance); + void addPortalCandidate(const Portal& portal); + + private: + NavigationStream* m_stream; +}; + } // namespace Acts diff --git a/Core/include/Acts/Navigation/SurfaceArrayNavigationPolicy.hpp b/Core/include/Acts/Navigation/SurfaceArrayNavigationPolicy.hpp new file mode 100644 index 00000000000..9143cf0087d --- /dev/null +++ b/Core/include/Acts/Navigation/SurfaceArrayNavigationPolicy.hpp @@ -0,0 +1,83 @@ +// 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 "Acts/Navigation/INavigationPolicy.hpp" + +#pragma once + +namespace Acts { + +class SurfaceArray; + +/// A navigation policy that internally uses the Gen1 @c SurfaceArray class +class SurfaceArrayNavigationPolicy : public INavigationPolicy { + public: + /// Enum for configuring which type of surface array to produce. This affects + /// the projection that is used for creating the binning structure. + enum class LayerType { Cylinder, Disc, Plane }; + + /// Config struct to configure the surface array navigation + struct Config { + /// The type of the layer + LayerType layerType = LayerType::Cylinder; + /// The number of bins in the local directions. The interpretation depends + /// on the layer type. + std::pair bins; + }; + + /// Main constructor, which internally creates the surface array acceleration + /// structure. + /// @note Expects that all relevant surfaces are registered with @p volume. + /// Only selects sensitive surfaces for the surface array. + /// @param gctx The geometry context + /// @param volume The *layer volume* to construct the surface array from + /// @param logger A logging instance + /// @param config The configuration for the surface array + explicit SurfaceArrayNavigationPolicy(const GeometryContext& gctx, + const TrackingVolume& volume, + const Logger& logger, Config config); + + /// Update the navigation state from the surface array + /// @param args The navigation arguments + /// @param stream The navigation stream to update + /// @param logger The logger + void initializeCandidates(const NavigationArguments& args, + AppendOnlyNavigationStream& stream, + const Logger& logger) const; + + /// Connect this policy with a navigation delegate + /// @param delegate The navigation delegate to connect to + void connect(NavigationDelegate& delegate) const override; + + /// Output stream operator for the contained layer type enum + /// @param os The output stream + /// @param layerType The layer type to print + friend std::ostream& operator<<(std::ostream& os, + const LayerType& layerType) { + switch (layerType) { + case LayerType::Cylinder: + os << "Cylinder"; + break; + case LayerType::Disc: + os << "Disc"; + break; + case LayerType::Plane: + os << "Plane"; + break; + } + return os; + } + + private: + std::unique_ptr m_surfaceArray{}; + const TrackingVolume& m_volume; +}; + +static_assert(NavigationPolicyConcept); + +} // namespace Acts diff --git a/Core/include/Acts/Navigation/TryAllNavigationPolicy.hpp b/Core/include/Acts/Navigation/TryAllNavigationPolicy.hpp new file mode 100644 index 00000000000..e7dc4bb3ddc --- /dev/null +++ b/Core/include/Acts/Navigation/TryAllNavigationPolicy.hpp @@ -0,0 +1,63 @@ +// 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/. + +#pragma once + +#include "Acts/Navigation/INavigationPolicy.hpp" +#include "Acts/Navigation/NavigationStream.hpp" + +namespace Acts { + +class TrackingVolume; +class GeometryContext; +class Logger; + +/// Policy which adds **all** candidates of the configured type to the +/// stream +class TryAllNavigationPolicy final : public INavigationPolicy { + public: + struct Config { + bool portals = true; + bool sensitives = true; + }; + + /// Constructor from a volume + /// @param config The configuration for the policy + /// @param gctx is the geometry context + /// @param volume is the volume to navigate + /// @param logger is the logger + TryAllNavigationPolicy(const Config& config, const GeometryContext& gctx, + const TrackingVolume& volume, const Logger& logger); + + /// Constructor from a volume + /// @param gctx is the geometry context + /// @param volume is the volume to navigate + /// @param logger is the logger + TryAllNavigationPolicy(const GeometryContext& gctx, + const TrackingVolume& volume, const Logger& logger); + + /// Add all candidates to the stream + /// @param args are the navigation arguments + /// @param stream is the navigation stream to update + /// @param logger is the logger + void initializeCandidates(const NavigationArguments& args, + AppendOnlyNavigationStream& stream, + const Logger& logger) const; + + /// Connect the policy to a navigation delegate + /// @param delegate is the navigation delegate + void connect(NavigationDelegate& delegate) const override; + + private: + Config m_cfg; + const TrackingVolume* m_volume; +}; + +static_assert(NavigationPolicyConcept); + +} // namespace Acts diff --git a/Core/src/Geometry/TrackingVolume.cpp b/Core/src/Geometry/TrackingVolume.cpp index e7d069b80c5..611904537f0 100644 --- a/Core/src/Geometry/TrackingVolume.cpp +++ b/Core/src/Geometry/TrackingVolume.cpp @@ -16,6 +16,8 @@ #include "Acts/Material/IMaterialDecorator.hpp" #include "Acts/Material/IVolumeMaterial.hpp" #include "Acts/Material/ProtoVolumeMaterial.hpp" +#include "Acts/Navigation/INavigationPolicy.hpp" +#include "Acts/Navigation/NavigationStream.hpp" #include "Acts/Propagator/Navigator.hpp" #include "Acts/Surfaces/RegularSurface.hpp" #include "Acts/Surfaces/Surface.hpp" @@ -28,6 +30,8 @@ #include #include +#include + namespace Acts { // constructor for arguments @@ -47,6 +51,10 @@ TrackingVolume::TrackingVolume( createBoundarySurfaces(); interlinkLayers(); connectDenseBoundarySurfaces(denseVolumeVector); + + DelegateChainBuilder{m_navigationDelegate} + .add<&INavigationPolicy::noopInitializeCandidates>() + .store(m_navigationDelegate); } TrackingVolume::TrackingVolume(Volume& volume, const std::string& volumeName) @@ -61,6 +69,8 @@ TrackingVolume::TrackingVolume(const Transform3& transform, {}, volumeName) {} TrackingVolume::~TrackingVolume() = default; +TrackingVolume::TrackingVolume(TrackingVolume&&) = default; +TrackingVolume& TrackingVolume::operator=(TrackingVolume&&) = default; const TrackingVolume* TrackingVolume::lowestTrackingVolume( const GeometryContext& gctx, const Vector3& position, @@ -735,4 +745,20 @@ void TrackingVolume::visualize(IVisualization3D& helper, } } +void TrackingVolume::setNavigationPolicy( + std::unique_ptr policy) { + if (policy == nullptr) { + throw std::invalid_argument("Navigation policy is nullptr"); + } + + m_navigationPolicy = std::move(policy); + m_navigationPolicy->connect(m_navigationDelegate); +} + +void TrackingVolume::initializeNavigationCandidates( + const NavigationArguments& args, AppendOnlyNavigationStream& stream, + const Logger& logger) const { + m_navigationDelegate(args, stream, logger); +} + } // namespace Acts diff --git a/Core/src/Navigation/CMakeLists.txt b/Core/src/Navigation/CMakeLists.txt index 81cbf616d44..72bbf65b853 100644 --- a/Core/src/Navigation/CMakeLists.txt +++ b/Core/src/Navigation/CMakeLists.txt @@ -1 +1,7 @@ -target_sources(ActsCore PRIVATE NavigationStream.cpp) +target_sources( + ActsCore + PRIVATE + NavigationStream.cpp + TryAllNavigationPolicy.cpp + SurfaceArrayNavigationPolicy.cpp +) diff --git a/Core/src/Navigation/NavigationStream.cpp b/Core/src/Navigation/NavigationStream.cpp index bf15367cd3b..c36eed12bbe 100644 --- a/Core/src/Navigation/NavigationStream.cpp +++ b/Core/src/Navigation/NavigationStream.cpp @@ -13,10 +13,12 @@ #include -bool Acts::NavigationStream::initialize(const GeometryContext& gctx, - const QueryPoint& queryPoint, - const BoundaryTolerance& cTolerance, - ActsScalar onSurfaceTolerance) { +namespace Acts { + +bool NavigationStream::initialize(const GeometryContext& gctx, + const QueryPoint& queryPoint, + const BoundaryTolerance& cTolerance, + ActsScalar onSurfaceTolerance) { // Position and direction from the query point const Vector3& position = queryPoint.position; const Vector3& direction = queryPoint.direction; @@ -36,7 +38,7 @@ bool Acts::NavigationStream::initialize(const GeometryContext& gctx, // A container collecting additional candidates from multiple // valid interseciton std::vector additionalCandidates = {}; - for (auto& [sIntersection, portal, bTolerance] : m_candidates) { + for (auto& [sIntersection, gen2Portal, portal, bTolerance] : m_candidates) { // Get the surface from the object intersection const Surface* surface = sIntersection.object(); // Intersect the surface @@ -58,7 +60,10 @@ bool Acts::NavigationStream::initialize(const GeometryContext& gctx, originalCandidateUpdated = true; } else { additionalCandidates.emplace_back( - Candidate{rsIntersection, portal, bTolerance}); + Candidate{.intersection = rsIntersection, + .gen2Portal = gen2Portal, + .portal = portal, + .bTolerance = bTolerance}); } } } @@ -74,7 +79,7 @@ bool Acts::NavigationStream::initialize(const GeometryContext& gctx, // The we find the first invalid candidate auto firstInvalid = std::ranges::find_if(m_candidates, [](const Candidate& a) { - const auto& [aIntersection, aPortal, aTolerance] = a; + const auto& [aIntersection, aGen2Portal, aPortal, aTolerance] = a; return !aIntersection.isValid(); }); @@ -88,32 +93,28 @@ bool Acts::NavigationStream::initialize(const GeometryContext& gctx, return true; } -bool Acts::NavigationStream::update(const GeometryContext& gctx, - const QueryPoint& queryPoint, - ActsScalar onSurfaceTolerance) { - // Position and direction from the query point - const Vector3& position = queryPoint.position; - const Vector3& direction = queryPoint.direction; - +bool NavigationStream::update(const GeometryContext& gctx, + const QueryPoint& queryPoint, + ActsScalar onSurfaceTolerance) { // Loop over the (currently valid) candidates and update for (; m_currentIndex < m_candidates.size(); ++m_currentIndex) { // Get the candidate, and resolve the tuple Candidate& candidate = currentCandidate(); - auto& [sIntersection, portal, bTolerance] = candidate; // Get the surface from the object intersection - const Surface* surface = sIntersection.object(); + const Surface* surface = candidate.intersection.object(); // (re-)Intersect the surface - auto multiIntersection = surface->intersect(gctx, position, direction, - bTolerance, onSurfaceTolerance); + auto multiIntersection = + surface->intersect(gctx, queryPoint.position, queryPoint.direction, + candidate.bTolerance, onSurfaceTolerance); // Split them into valid intersections for (const auto& rsIntersection : multiIntersection.split()) { // Skip wrong index solution - if (rsIntersection.index() != sIntersection.index()) { + if (rsIntersection.index() != candidate.intersection.index()) { continue; } // Valid solution is either on surface or updates the distance if (rsIntersection.isValid()) { - sIntersection = rsIntersection; + candidate.intersection = rsIntersection; return true; } } @@ -122,32 +123,57 @@ bool Acts::NavigationStream::update(const GeometryContext& gctx, return false; } -void Acts::NavigationStream::addSurfaceCandidate( - const Surface* surface, const BoundaryTolerance& bTolerance) { - m_candidates.emplace_back(Candidate{ - ObjectIntersection::invalid(surface), nullptr, bTolerance}); +void NavigationStream::addSurfaceCandidate( + const Surface& surface, const BoundaryTolerance& bTolerance) { + m_candidates.emplace_back( + Candidate{.intersection = ObjectIntersection::invalid(&surface), + .bTolerance = bTolerance}); } -void Acts::NavigationStream::addSurfaceCandidates( - const std::vector& surfaces, - const BoundaryTolerance& bTolerance) { +void NavigationStream::addSurfaceCandidates( + std::span surfaces, const BoundaryTolerance& bTolerance) { std::ranges::for_each(surfaces, [&](const auto* surface) { - m_candidates.emplace_back(Candidate{ - ObjectIntersection::invalid(surface), nullptr, bTolerance}); + m_candidates.emplace_back( + Candidate{.intersection = ObjectIntersection::invalid(surface), + .bTolerance = bTolerance}); }); } -void Acts::NavigationStream::addPortalCandidate(const Portal* portal) { - m_candidates.emplace_back( - Candidate{ObjectIntersection::invalid(&(portal->surface())), - portal, BoundaryTolerance::None()}); +void NavigationStream::addPortalCandidate(const Experimental::Portal& portal) { + m_candidates.emplace_back(Candidate{ + .intersection = ObjectIntersection::invalid(&portal.surface()), + .gen2Portal = &portal, + .bTolerance = BoundaryTolerance::None()}); +} + +void NavigationStream::addPortalCandidate(const Portal& portal) { + m_candidates.emplace_back(Candidate{ + .intersection = ObjectIntersection::invalid(&portal.surface()), + .portal = &portal, + .bTolerance = BoundaryTolerance::None()}); } -void Acts::NavigationStream::addPortalCandidates( - const std::vector& portals) { +void NavigationStream::addPortalCandidates( + std::span portals) { std::ranges::for_each(portals, [&](const auto& portal) { - m_candidates.emplace_back( - Candidate{ObjectIntersection::invalid(&(portal->surface())), - portal, BoundaryTolerance::None()}); + m_candidates.emplace_back(Candidate{ + .intersection = + ObjectIntersection::invalid(&(portal->surface())), + .gen2Portal = portal, + .bTolerance = BoundaryTolerance::None()}); }); } + +AppendOnlyNavigationStream::AppendOnlyNavigationStream(NavigationStream& stream) + : m_stream{&stream} {} + +void AppendOnlyNavigationStream::addPortalCandidate(const Portal& portal) { + m_stream->addPortalCandidate(portal); +} + +void AppendOnlyNavigationStream::addSurfaceCandidate( + const Surface& surface, const BoundaryTolerance& bTolerance) { + m_stream->addSurfaceCandidate(surface, bTolerance); +} + +} // namespace Acts diff --git a/Core/src/Navigation/SurfaceArrayNavigationPolicy.cpp b/Core/src/Navigation/SurfaceArrayNavigationPolicy.cpp new file mode 100644 index 00000000000..f40621fdd06 --- /dev/null +++ b/Core/src/Navigation/SurfaceArrayNavigationPolicy.cpp @@ -0,0 +1,83 @@ +// 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 "Acts/Navigation/SurfaceArrayNavigationPolicy.hpp" + +#include "Acts/Geometry/SurfaceArrayCreator.hpp" +#include "Acts/Geometry/TrackingVolume.hpp" +#include "Acts/Navigation/NavigationStream.hpp" + +#include + +namespace Acts { + +SurfaceArrayNavigationPolicy::SurfaceArrayNavigationPolicy( + const GeometryContext& gctx, const TrackingVolume& volume, + const Logger& logger, Config config) + : m_volume(volume) { + ACTS_VERBOSE("Constructing SurfaceArrayNavigationPolicy for volume " + << volume.volumeName()); + ACTS_VERBOSE("~> Layer type is " << config.layerType); + ACTS_VERBOSE("~> bins: " << config.bins.first << " x " << config.bins.second); + + SurfaceArrayCreator::Config sacConfig; + SurfaceArrayCreator sac{sacConfig, logger.clone("SrfArrCrtr")}; + + std::vector> surfaces; + surfaces.reserve(volume.surfaces().size()); + for (const auto& surface : volume.surfaces()) { + if (surface.associatedDetectorElement() == nullptr) { + continue; + } + surfaces.push_back(surface.getSharedPtr()); + } + + if (config.layerType == LayerType::Disc) { + auto [binsR, binsPhi] = config.bins; + m_surfaceArray = + sac.surfaceArrayOnDisc(gctx, std::move(surfaces), binsPhi, binsR); + } else if (config.layerType == LayerType::Cylinder) { + auto [binsPhi, binsZ] = config.bins; + m_surfaceArray = + sac.surfaceArrayOnCylinder(gctx, std::move(surfaces), binsPhi, binsZ); + // m_surfaces = sac.createCylinderSurfaces(config.bins.first, + // config.bins.second); + } else if (config.layerType == LayerType::Plane) { + ACTS_ERROR("Plane layers are not yet supported"); + throw std::invalid_argument("Plane layers are not yet supported"); + } else { + throw std::invalid_argument("Unknown layer type"); + } + + if (!m_surfaceArray) { + ACTS_ERROR("Failed to create surface array"); + throw std::runtime_error("Failed to create surface array"); + } +} + +void SurfaceArrayNavigationPolicy::initializeCandidates( + const NavigationArguments& args, AppendOnlyNavigationStream& stream, + const Logger& logger) const { + ACTS_VERBOSE("SrfArrNavPol (volume=" << m_volume.volumeName() << ")"); + + ACTS_VERBOSE("Querying sensitive surfaces at " << args.position.transpose()); + const std::vector& sensitiveSurfaces = + m_surfaceArray->neighbors(args.position); + ACTS_VERBOSE("~> Surface array reports " << sensitiveSurfaces.size() + << " sensitive surfaces"); + + for (const auto* surface : sensitiveSurfaces) { + stream.addSurfaceCandidate(*surface, args.tolerance); + }; +} + +void SurfaceArrayNavigationPolicy::connect(NavigationDelegate& delegate) const { + connectDefault(delegate); +} + +} // namespace Acts diff --git a/Core/src/Navigation/TryAllNavigationPolicy.cpp b/Core/src/Navigation/TryAllNavigationPolicy.cpp new file mode 100644 index 00000000000..40ca70c4079 --- /dev/null +++ b/Core/src/Navigation/TryAllNavigationPolicy.cpp @@ -0,0 +1,54 @@ +// 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 "Acts/Navigation/TryAllNavigationPolicy.hpp" + +#include "Acts/Geometry/TrackingVolume.hpp" +#include "Acts/Navigation/NavigationStream.hpp" + +namespace Acts { + +TryAllNavigationPolicy::TryAllNavigationPolicy(const Config& config, + const GeometryContext& /*gctx*/, + const TrackingVolume& volume, + const Logger& logger) + : m_cfg{config}, m_volume(&volume) { + assert(m_volume != nullptr); + ACTS_VERBOSE("TryAllNavigationPolicy created for volume " + << m_volume->volumeName()); +} + +TryAllNavigationPolicy::TryAllNavigationPolicy(const GeometryContext& gctx, + const TrackingVolume& volume, + const Logger& logger) + : TryAllNavigationPolicy({}, gctx, volume, logger) {} + +void TryAllNavigationPolicy::initializeCandidates( + const NavigationArguments& args, AppendOnlyNavigationStream& stream, + const Logger& logger) const { + ACTS_VERBOSE("TryAllNavigationPolicy"); + assert(m_volume != nullptr); + + if (m_cfg.portals) { + for (const auto& portal : m_volume->portals()) { + stream.addPortalCandidate(portal); + } + } + + if (m_cfg.sensitives) { + for (const auto& surface : m_volume->surfaces()) { + stream.addSurfaceCandidate(surface, args.tolerance); + }; + } +} + +void TryAllNavigationPolicy::connect(NavigationDelegate& delegate) const { + connectDefault(delegate); +} + +} // namespace Acts diff --git a/Examples/Python/CMakeLists.txt b/Examples/Python/CMakeLists.txt index 0a834ae546f..0eb4b1ff4ec 100644 --- a/Examples/Python/CMakeLists.txt +++ b/Examples/Python/CMakeLists.txt @@ -21,6 +21,7 @@ pybind11_add_module(ActsPythonBindings src/Output.cpp src/Input.cpp src/Propagation.cpp + src/Navigation.cpp src/Generators.cpp src/Obj.cpp src/TruthTracking.cpp diff --git a/Examples/Python/src/ModuleEntry.cpp b/Examples/Python/src/ModuleEntry.cpp index 1a0e27db907..a74d277f7a5 100644 --- a/Examples/Python/src/ModuleEntry.cpp +++ b/Examples/Python/src/ModuleEntry.cpp @@ -50,6 +50,7 @@ void addBinning(Context& ctx); void addEventData(Context& ctx); void addPropagation(Context& ctx); +void addNavigation(Context& ctx); void addGeometry(Context& ctx); void addGeometryBuildingGen1(Context& ctx); @@ -123,6 +124,7 @@ PYBIND11_MODULE(ActsPythonBindings, m) { addOutput(ctx); addPropagation(ctx); + addNavigation(ctx); addGeometryBuildingGen1(ctx); addGeometry(ctx); addExperimentalGeometry(ctx); diff --git a/Examples/Python/src/Navigation.cpp b/Examples/Python/src/Navigation.cpp new file mode 100644 index 00000000000..ac2f1a77ef6 --- /dev/null +++ b/Examples/Python/src/Navigation.cpp @@ -0,0 +1,203 @@ +// 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 "Acts/Geometry/CylinderVolumeBounds.hpp" +#include "Acts/Geometry/NavigationPolicyFactory.hpp" +#include "Acts/Geometry/TrackingVolume.hpp" +#include "Acts/Navigation/SurfaceArrayNavigationPolicy.hpp" +#include "Acts/Navigation/TryAllNavigationPolicy.hpp" +#include "Acts/Plugins/Python/Utilities.hpp" +#include "Acts/Surfaces/CylinderBounds.hpp" +#include "Acts/Surfaces/CylinderSurface.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "Acts/Utilities/TypeTag.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace py = pybind11; +using namespace pybind11::literals; + +namespace Acts::Python { + +struct AnyNavigationPolicyFactory : public Acts::NavigationPolicyFactory { + virtual std::unique_ptr add( + TypeTag /*type*/) = 0; + + virtual std::unique_ptr add( + TypeTag /*type*/, + SurfaceArrayNavigationPolicy::Config config) = 0; +}; + +template , + typename... Policies> +struct NavigationPolicyFactoryT : public AnyNavigationPolicyFactory { + NavigationPolicyFactoryT(Factory impl) + requires(sizeof...(Policies) > 0) + : m_impl(std::move(impl)) {} + + NavigationPolicyFactoryT() + requires(sizeof...(Policies) == 0) + : m_impl{} {} + + std::unique_ptr add( + TypeTag /*type*/) override { + return add(); + } + + std::unique_ptr add( + TypeTag /*type*/, + SurfaceArrayNavigationPolicy::Config config) override { + return add(std::move(config)); + } + + std::unique_ptr build( + const GeometryContext& gctx, const TrackingVolume& volume, + const Logger& logger) const override { + if constexpr (sizeof...(Policies) > 0) { + return m_impl.build(gctx, volume, logger); + } else { + throw std::runtime_error("No policies added to the factory"); + } + } + + private: + template + std::unique_ptr add(Args&&... args) { + if constexpr (!((std::is_same_v || ...))) { + auto impl = + std::move(m_impl).template add(std::forward(args)...); + return std::make_unique< + NavigationPolicyFactoryT>( + std::move(impl)); + } else { + throw std::invalid_argument("Policy already added to the factory"); + } + } + + Factory m_impl; +}; + +class NavigationPolicyFactory : public Acts::NavigationPolicyFactory { + public: + // This overload is for all the navigation policies that don't have extra + // arguments + NavigationPolicyFactory& addNoArguments(const py::object& cls) { + auto m = py::module_::import("acts"); + if (py::object o = m.attr("TryAllNavigationPolicy"); cls.is(o)) { + m_impl = m_impl->add(Type); + } + // Add other policies here + return *this; + } + + NavigationPolicyFactory& addSurfaceArray( + const py::object& /*cls*/, + const SurfaceArrayNavigationPolicy::Config& config) { + m_impl = m_impl->add(Type, config); + return *this; + } + + std::unique_ptr build( + const GeometryContext& gctx, const TrackingVolume& volume, + const Logger& logger) const override { + return m_impl->build(gctx, volume, logger); + } + + private: + std::unique_ptr m_impl = + std::make_unique>(); +}; + +namespace Test { +class DetectorElementStub : public DetectorElementBase { + public: + DetectorElementStub() : DetectorElementBase() {} + + const Transform3& transform(const GeometryContext&) const override { + return m_transform; + } + + /// Return surface representation - const return pattern + const Surface& surface() const override { + throw std::runtime_error("Not implemented"); + } + + /// Non-const return pattern + Surface& surface() override { throw std::runtime_error("Not implemented"); } + + /// Returns the thickness of the module + /// @return double that indicates the thickness of the module + double thickness() const override { return 0; } + + private: + Transform3 m_transform; +}; + +} // namespace Test + +void addNavigation(Context& ctx) { + auto m = ctx.get("main"); + + py::class_>( + m, "_NavigationPolicyFactory"); + + py::class_(m, "TryAllNavigationPolicy"); + + py::class_>( + m, "NavigationPolicyFactory") + // only to mirror the C++ API + .def_static("make", []() { return NavigationPolicyFactory{}; }) + .def("add", &NavigationPolicyFactory::addNoArguments) + .def("add", &NavigationPolicyFactory::addSurfaceArray) + .def("_buildTest", [](NavigationPolicyFactory& self) { + auto vol1 = std::make_shared( + Transform3::Identity(), + std::make_shared(30, 40, 100)); + vol1->setVolumeName("TestVolume"); + + auto detElem = std::make_unique(); + + auto surface = Surface::makeShared( + Transform3::Identity(), std::make_shared(30, 40)); + surface->assignDetectorElement(*detElem); + + vol1->addSurface(std::move(surface)); + + std::unique_ptr result = + self.build(GeometryContext{}, *vol1, + *getDefaultLogger("Test", Logging::VERBOSE)); + }); + + { + auto saPolicy = py::class_( + m, "SurfaceArrayNavigationPolicy"); + + using LayerType = SurfaceArrayNavigationPolicy::LayerType; + py::enum_(saPolicy, "LayerType") + .value("Cylinder", LayerType::Cylinder) + .value("Disc", LayerType::Disc) + .value("Plane", LayerType::Plane); + + using Config = SurfaceArrayNavigationPolicy::Config; + auto c = py::class_(saPolicy, "Config").def(py::init<>()); + ACTS_PYTHON_STRUCT_BEGIN(c, Config); + ACTS_PYTHON_MEMBER(layerType); + ACTS_PYTHON_MEMBER(bins); + ACTS_PYTHON_STRUCT_END(); + } +} + +} // namespace Acts::Python diff --git a/Examples/Python/tests/test_navigation.py b/Examples/Python/tests/test_navigation.py new file mode 100644 index 00000000000..55ec8ac9540 --- /dev/null +++ b/Examples/Python/tests/test_navigation.py @@ -0,0 +1,42 @@ +import pytest + +import acts + +import acts.examples + + +def test_navigation_policy_factory(): + + policy = ( + acts.NavigationPolicyFactory.make() + .add(acts.TryAllNavigationPolicy) + .add( + acts.SurfaceArrayNavigationPolicy, + acts.SurfaceArrayNavigationPolicy.Config( + layerType=acts.SurfaceArrayNavigationPolicy.LayerType.Disc, + bins=(10, 10), + ), + ) + ) + + policy._buildTest() + + policy = acts.NavigationPolicyFactory.make().add(acts.TryAllNavigationPolicy) + + policy._buildTest() + + +def test_navigation_policy_factory_build_empty(): + policy = acts.NavigationPolicyFactory.make() + + with pytest.raises(RuntimeError): + policy._buildTest() + + +def test_navigation_policy_factory_add_multiple(): + with pytest.raises(ValueError): + ( + acts.NavigationPolicyFactory.make() + .add(acts.TryAllNavigationPolicy) + .add(acts.TryAllNavigationPolicy) + ) diff --git a/Tests/UnitTests/Core/Navigation/CMakeLists.txt b/Tests/UnitTests/Core/Navigation/CMakeLists.txt index edca953321d..a18c2d34c9c 100644 --- a/Tests/UnitTests/Core/Navigation/CMakeLists.txt +++ b/Tests/UnitTests/Core/Navigation/CMakeLists.txt @@ -5,3 +5,4 @@ add_unittest(NavigationStream NavigationStreamTests.cpp) add_unittest(NavigationStateUpdaters NavigationStateUpdatersTests.cpp) add_unittest(DetectorNavigator DetectorNavigatorTests.cpp) add_unittest(MultiWireNavigation MultiWireNavigationTests.cpp) +add_unittest(NavigationPolicy NavigationPolicyTests.cpp) diff --git a/Tests/UnitTests/Core/Navigation/NavigationPolicyTests.cpp b/Tests/UnitTests/Core/Navigation/NavigationPolicyTests.cpp new file mode 100644 index 00000000000..181e1ad2a1f --- /dev/null +++ b/Tests/UnitTests/Core/Navigation/NavigationPolicyTests.cpp @@ -0,0 +1,240 @@ +// 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 + +#include "Acts/Definitions/Units.hpp" +#include "Acts/Geometry/CylinderVolumeBounds.hpp" +#include "Acts/Geometry/NavigationPolicyFactory.hpp" +#include "Acts/Geometry/TrackingVolume.hpp" +#include "Acts/Navigation/INavigationPolicy.hpp" +#include "Acts/Navigation/MultiNavigationPolicy.hpp" +#include "Acts/Navigation/NavigationDelegate.hpp" +#include "Acts/Navigation/NavigationStream.hpp" +#include "Acts/Navigation/TryAllNavigationPolicy.hpp" + +using namespace Acts; +using namespace Acts::UnitLiterals; + +BOOST_AUTO_TEST_SUITE(NavigationPolicyTests) + +GeometryContext gctx; +auto logger = getDefaultLogger("NavigationPolicyTests", Logging::VERBOSE); + +struct APolicy : public INavigationPolicy { + APolicy(const GeometryContext& /*gctx*/, const TrackingVolume& /*volume*/, + const Logger& /*logger*/) {} + + void initializeCandidates(const NavigationArguments& /*unused*/, + AppendOnlyNavigationStream& /*unused*/, + const Logger& /*unused*/) const { + const_cast(this)->executed = true; + } + + void connect(NavigationDelegate& delegate) const override { + connectDefault(delegate); + } + + bool executed = false; +}; + +struct BPolicy : public INavigationPolicy { + struct Config { + int value; + }; + + BPolicy(const GeometryContext& /*gctx*/, const TrackingVolume& /*volume*/, + const Logger& /*logger*/, Config config) + : m_config(config) {} + + void connect(NavigationDelegate& delegate) const override { + connectDefault(delegate); + } + + void initializeCandidates(const NavigationArguments& /*unused*/, + AppendOnlyNavigationStream& /*unused*/, + const Logger& /*unused*/) const { + const_cast(this)->executed = true; + const_cast(this)->value = m_config.value; + } + + bool executed = false; + int value = 0; + + Config m_config; +}; + +BOOST_AUTO_TEST_CASE(DirectTest) { + TrackingVolume volume{ + Transform3::Identity(), + std::make_shared(250_mm, 400_mm, 310_mm), + "PixelLayer3"}; + + MultiNavigationPolicy policy{APolicy{gctx, volume, *logger}, + BPolicy{gctx, volume, *logger, {.value = 4242}}}; + + NavigationDelegate delegate; + policy.connect(delegate); + + NavigationStream main; + AppendOnlyNavigationStream stream{main}; + delegate(NavigationArguments{.position = Vector3::Zero(), + .direction = Vector3::Zero()}, + stream, *logger); + + BOOST_CHECK(std::get(policy.policies()).executed); + BOOST_CHECK(std::get(policy.policies()).executed); + BOOST_CHECK_EQUAL(std::get(policy.policies()).value, 4242); +} + +BOOST_AUTO_TEST_CASE(FactoryTest) { + TrackingVolume volume{ + Transform3::Identity(), + std::make_shared(250_mm, 400_mm, 310_mm), + "PixelLayer3"}; + + BPolicy::Config config{.value = 42}; + + std::function( + const GeometryContext&, const TrackingVolume&, const Logger&)> + factory = NavigationPolicyFactory::make() + .add() // no arguments + .add(config); // config struct as argument + + auto policyBase = factory(gctx, volume, *logger); + auto policyBase2 = factory(gctx, volume, *logger); + + auto& policy = + dynamic_cast&>(*policyBase); + + NavigationDelegate delegate; + policy.connect(delegate); + + NavigationStream main; + AppendOnlyNavigationStream stream{main}; + delegate(NavigationArguments{.position = Vector3::Zero(), + .direction = Vector3::Zero()}, + stream, *logger); + + BOOST_CHECK(std::get(policy.policies()).executed); + BOOST_CHECK(std::get(policy.policies()).executed); + BOOST_CHECK_EQUAL(std::get(policy.policies()).value, 42); + + auto& policy2 = + dynamic_cast&>(*policyBase2); + + NavigationDelegate delegate2; + policyBase2->connect(delegate2); + + delegate2(NavigationArguments{.position = Vector3::Zero(), + .direction = Vector3::Zero()}, + stream, *logger); + + BOOST_CHECK(std::get(policy2.policies()).executed); + BOOST_CHECK(std::get(policy2.policies()).executed); + BOOST_CHECK_EQUAL(std::get(policy2.policies()).value, 42); +} + +BOOST_AUTO_TEST_CASE(AsUniquePtrTest) { + TrackingVolume volume{ + Transform3::Identity(), + std::make_shared(250_mm, 400_mm, 310_mm), + "PixelLayer3"}; + + std::unique_ptr factory = + NavigationPolicyFactory::make().add().asUniquePtr(); + + auto policyBase = factory->build(gctx, volume, *logger); + auto& policy = dynamic_cast&>(*policyBase); + + NavigationDelegate delegate; + policyBase->connect(delegate); + + NavigationStream main; + AppendOnlyNavigationStream stream{main}; + delegate(NavigationArguments{.position = Vector3::Zero(), + .direction = Vector3::Zero()}, + stream, *logger); + + BOOST_CHECK(std::get(policy.policies()).executed); +} + +struct CPolicy : public INavigationPolicy {}; + +template +struct CPolicySpecialized : public CPolicy { + struct Config { + T value; + }; + + CPolicySpecialized(const TrackingVolume& /*volume*/, Config config) + : m_config(config) {} + + void connect(NavigationDelegate& delegate) const override { + connectDefault>(delegate); + } + + void initializeCandidates(const NavigationArguments& /*unused*/, + AppendOnlyNavigationStream& /*stream*/, + const Logger& /*logger*/) const { + auto* self = const_cast*>(this); + self->executed = true; + self->value = m_config.value; + } + + bool executed = false; + int value = 0; + + Config m_config; +}; + +struct IsolatedConfig { + int value; +}; + +auto makeCPolicy(const GeometryContext& /*gctx*/, const TrackingVolume& volume, + const Logger& /*logger*/, IsolatedConfig config) { + // I can do arbitrary stuff here + CPolicySpecialized::Config config2{.value = config.value}; + return CPolicySpecialized(volume, config2); +} + +BOOST_AUTO_TEST_CASE(IsolatedFactory) { + TrackingVolume volume{ + Transform3::Identity(), + std::make_shared(250_mm, 400_mm, 310_mm), + "PixelLayer3"}; + + IsolatedConfig config{.value = 44}; + auto factory = + NavigationPolicyFactory::make().add().add(makeCPolicy, config); + + auto factory2 = + NavigationPolicyFactory::make().add(makeCPolicy, config).add(); + + auto policyBase = factory(gctx, volume, *logger); + auto& policy = + dynamic_cast>&>( + *policyBase); + + NavigationDelegate delegate; + policyBase->connect(delegate); + + NavigationStream main; + AppendOnlyNavigationStream stream{main}; + delegate(NavigationArguments{.position = Vector3::Zero(), + .direction = Vector3::Zero()}, + stream, *logger); + + BOOST_CHECK(std::get(policy.policies()).executed); + BOOST_CHECK(std::get>(policy.policies()).executed); + BOOST_CHECK_EQUAL(std::get>(policy.policies()).value, + 44); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Core/Navigation/NavigationStreamTests.cpp b/Tests/UnitTests/Core/Navigation/NavigationStreamTests.cpp index 097dde2ed64..9ca578b0aba 100644 --- a/Tests/UnitTests/Core/Navigation/NavigationStreamTests.cpp +++ b/Tests/UnitTests/Core/Navigation/NavigationStreamTests.cpp @@ -94,7 +94,7 @@ BOOST_AUTO_TEST_CASE(NavigationStream_InitializePlanes) { NavigationStream nStreamTemplate; for (const auto& surface : surfaces) { - nStreamTemplate.addSurfaceCandidate(surface.get(), + nStreamTemplate.addSurfaceCandidate(*surface, Acts::BoundaryTolerance::None()); } BOOST_CHECK_EQUAL(nStreamTemplate.remainingCandidates(), 4u); @@ -140,7 +140,7 @@ BOOST_AUTO_TEST_CASE(NavigationStream_InitializePlanes) { // (5) Test de-duplication nStream = nStreamTemplate; - nStreamTemplate.addSurfaceCandidate(surfaces[0].get(), + nStreamTemplate.addSurfaceCandidate(*surfaces.at(0), Acts::BoundaryTolerance::None()); // One surface is duplicated in the stream BOOST_CHECK_EQUAL(nStreamTemplate.remainingCandidates(), 5u); @@ -159,7 +159,7 @@ BOOST_AUTO_TEST_CASE(NavigationStream_UpdatePlanes) { // reachable and intersections inside bounds NavigationStream nStreamTemplate; for (const auto& surface : surfaces) { - nStreamTemplate.addSurfaceCandidate(surface.get(), + nStreamTemplate.addSurfaceCandidate(*surface, Acts::BoundaryTolerance::None()); } BOOST_CHECK_EQUAL(nStreamTemplate.remainingCandidates(), 4u); @@ -231,7 +231,8 @@ BOOST_AUTO_TEST_CASE(NavigationStream_InitializeCylinders) { // Let us fill the surfaces into the navigation stream NavigationStream nStreamTemplate; for (const auto& surface : surfaces) { - nStreamTemplate.addSurfaceCandidates({surface.get()}, + const Surface* pointer = surface.get(); + nStreamTemplate.addSurfaceCandidates({&pointer, 1}, Acts::BoundaryTolerance::None()); } BOOST_CHECK_EQUAL(nStreamTemplate.remainingCandidates(), 4u);