Skip to content

Commit

Permalink
feat(nav): Gen3 Navigation policies and factory (#3760)
Browse files Browse the repository at this point in the history
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<TryAllPortalNavigationPolicy>()
    .add<SurfaceArrayNavigationPolicy>(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
  • Loading branch information
paulgessinger authored Oct 24, 2024
1 parent 6d982d0 commit c34520f
Show file tree
Hide file tree
Showing 20 changed files with 1,324 additions and 55 deletions.
220 changes: 220 additions & 0 deletions Core/include/Acts/Geometry/NavigationPolicyFactory.hpp
Original file line number Diff line number Diff line change
@@ -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 <concepts>
#include <memory>
namespace Acts {

class TrackingVolume;
class GeometryContext;
class Logger;
class INavigationPolicy;

namespace detail {
template <typename... Factories>
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<NavigationPolicy1>(arg1, arg2)
/// .add<NavigationPolicy2>(/*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<INavigationPolicy> build(
const GeometryContext& gctx, const TrackingVolume& volume,
const Logger& logger) const = 0;
};

namespace detail {

template <typename F, typename... Args>
concept NavigationPolicyIsolatedFactoryConcept = requires(F f) {
{
f(std::declval<const GeometryContext&>(),
std::declval<const TrackingVolume&>(), std::declval<const Logger&>(),
std::declval<Args>()...)
} -> std::derived_from<INavigationPolicy>;

requires NavigationPolicyConcept<decltype(f(
std::declval<const GeometryContext&>(),
std::declval<const TrackingVolume&>(), std::declval<const Logger&>(),
std::declval<Args>()...))>;

requires(std::is_copy_constructible_v<Args> && ...);
};

template <>
class NavigationPolicyFactoryImpl<> {
public:
template <typename...>
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 <NavigationPolicyConcept P, typename... Args>
requires(std::is_constructible_v<P, const GeometryContext&,
const TrackingVolume&, const Logger&,
Args...> &&
(std::is_copy_constructible_v<Args> && ...))
constexpr auto add(Args&&... args) && {
auto factory = [=](const GeometryContext& gctx,
const TrackingVolume& volume, const Logger& logger) {
return P{gctx, volume, logger, args...};
};

return NavigationPolicyFactoryImpl<decltype(factory)>{
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 <typename Fn, typename... Args>
requires(NavigationPolicyIsolatedFactoryConcept<Fn, Args...>)
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<decltype(factory)>{
std::make_tuple(std::move(factory))};
}
};

template <typename F, typename... Fs>
class NavigationPolicyFactoryImpl<F, Fs...> : 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 <NavigationPolicyConcept P, typename... Args>
requires(std::is_constructible_v<P, const GeometryContext&,
const TrackingVolume&, const Logger&,
Args...> &&
(std::is_copy_constructible_v<Args> && ...))
constexpr auto add(Args&&... args) && {
auto factory = [=](const GeometryContext& gctx,
const TrackingVolume& volume, const Logger& logger) {
return P{gctx, volume, logger, args...};
};

return NavigationPolicyFactoryImpl<F, Fs..., decltype(factory)>{
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 <typename Fn, typename... Args>
requires(NavigationPolicyIsolatedFactoryConcept<Fn, Args...>)
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<F, Fs..., decltype(factory)>{
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<NavigationPolicyFactoryImpl<F, Fs...>>
asUniquePtr() && {
return std::make_unique<NavigationPolicyFactoryImpl<F, Fs...>>(
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<const GeometryContext&>(),
std::declval<const TrackingVolume&>(),
std::declval<const Logger&>())...});

// ... 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<policy_type>(
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<INavigationPolicy> build(
const GeometryContext& gctx, const TrackingVolume& volume,
const Logger& logger) const override {
return operator()(gctx, volume, logger);
}

private:
template <typename...>
friend class NavigationPolicyFactoryImpl;

NavigationPolicyFactoryImpl(std::tuple<F, Fs...>&& factories)
: m_factories(std::move(factories)) {}

std::tuple<F, Fs...> m_factories;
};

} // namespace detail

inline auto NavigationPolicyFactory::make() {
return detail::NavigationPolicyFactoryImpl<>{};
}

} // namespace Acts
32 changes: 25 additions & 7 deletions Core/include/Acts/Geometry/TrackingVolume.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -35,22 +35,22 @@
#include <utility>
#include <vector>

#include <boost/container/small_vector.hpp>
#include <boost/container/container_fwd.hpp>

namespace Acts {

class GlueVolumesDescriptor;
class VolumeBounds;
template <typename object_t>
struct NavigationOptions;
class GeometryIdentifier;
class IMaterialDecorator;
class ISurfaceMaterial;
class IVolumeMaterial;
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<INavigationPolicy> 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);
Expand Down Expand Up @@ -561,6 +575,10 @@ class TrackingVolume : public Volume {
std::vector<std::unique_ptr<TrackingVolume>> m_volumes;
std::vector<std::shared_ptr<Portal>> m_portals;
std::vector<std::shared_ptr<Surface>> m_surfaces;

std::unique_ptr<INavigationPolicy> m_navigationPolicy;

NavigationDelegate m_navigationDelegate{};
};

} // namespace Acts
73 changes: 73 additions & 0 deletions Core/include/Acts/Navigation/INavigationPolicy.hpp
Original file line number Diff line number Diff line change
@@ -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 <type_traits>

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 <typename T>
concept NavigationPolicyConcept = requires {
requires std::is_base_of_v<INavigationPolicy, T>;
// Has a conforming update method
requires requires(T policy, const NavigationArguments& args) {
policy.initializeCandidates(args,
std::declval<AppendOnlyNavigationStream&>(),
std::declval<const Logger&>());
};
};

/// 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 <NavigationPolicyConcept T>
void connectDefault(NavigationDelegate& delegate) const {
// This cannot be a concept because we use it in CRTP below
const auto* self = static_cast<const T*>(this);
DelegateChainBuilder{delegate}.add<&T::initializeCandidates>(self).store(
delegate);
}
};

} // namespace Acts
Loading

0 comments on commit c34520f

Please sign in to comment.