Skip to content

Commit

Permalink
feat: Geant4 Gdml surface provider + Unit Test (acts-project#2906)
Browse files Browse the repository at this point in the history
Adding a Surface Provider that is able to read Gdml files and extract surfaces based on Geant4PhysicalVolumeSelector criteria. Optional range-based selection of a subset of surfaces from the preselected, based on these criteria, set  is available. KDTree is used to implement the range-based subset selection.  

Unit test showing examples of interfacing between the volume selectors and internal structure builders with the surface provider is implemented. New selector that accepts G4 volumes based on their position is shipped to show the more memory-efficient preselection->KDTree selection pipeline. Converter to BinningValue to Geant4 axis is added and can be used to implement more sophisticated Volume Selectors.

Minor changes made to KdtSurfacesProvider and Range1D. In the former one of the internal variables is renamed to resolve shadowed declaration conflict with one of the necessary Geant4 includes. In the latter constructor is changed to be consistent with its description and provide a way to deduce uninitialized ranges based on the "degenerate" method.
  • Loading branch information
ssdetlab authored and asalzburger committed May 21, 2024
1 parent 40485d2 commit 16adb43
Show file tree
Hide file tree
Showing 6 changed files with 519 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Core/include/Acts/Detector/KdtSurfacesProvider.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class KdtSurfaces {
std::vector<std::shared_ptr<Surface>> surfacePtrs;
auto surfaceQuery = m_kdt->rangeSearchWithKey(range);
std::for_each(surfaceQuery.begin(), surfaceQuery.end(),
[&](auto& s) { surfacePtrs.push_back(s.second); });
[&](auto& surf) { surfacePtrs.push_back(surf.second); });
return surfacePtrs;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,38 @@

#pragma once

#include "Acts/Utilities/BinningType.hpp"
#include "Acts/Utilities/Delegate.hpp"

#include <array>
#include <string>
#include <vector>

#include "G4VPhysicalVolume.hh"

namespace Acts {

/// @brief Convert Acts binning value to Geant4 axis
/// as Geant4 uses a different axis convention
/// @param bv the Acts binning value
EAxis binToGeant4Axis(const Acts::BinningValue& bv) {
switch (bv) {
case Acts::BinningValue::binX:
return EAxis::kXAxis;
case Acts::BinningValue::binY:
return EAxis::kYAxis;
case Acts::BinningValue::binZ:
return EAxis::kZAxis;
case Acts::BinningValue::binR:
return EAxis::kRho;
case Acts::BinningValue::binPhi:
return EAxis::kPhi;
default:
throw std::invalid_argument(
"No Geant4 axis conversion for this binning value");
}
}

/// Interface class for selectors from physical volumes
class IGeant4PhysicalVolumeSelector {
public:
Expand Down Expand Up @@ -63,5 +86,42 @@ struct NameSelector : public IGeant4PhysicalVolumeSelector {
}
};

/// @brief Struct that selects G4VPhysicalVolume objects
/// based on the allowed range of their position
///
/// @note Can be used for preselection of volumes
/// before a KDTree search. This way the memory
/// consumption can be reduced, compromising the
/// execution speed
///
/// @note Careful with axis conventions as
/// Geant4 uses a different one than Acts
struct PositionSelector : public IGeant4PhysicalVolumeSelector {
std::map<unsigned int, std::tuple<double, double>> m_ranges;

/// Constructor with arguments
/// @param ranges the provided map of axes of ranges
PositionSelector(
const std::map<unsigned int, std::tuple<double, double>>& ranges)
: m_ranges(ranges) {}

/// Secect function for the volume
/// @param g4PhysVol the volume that is checked
/// @return a boolean indicating the selection
bool select(const G4VPhysicalVolume& g4PhysVol) const final {
bool matched = false;
G4ThreeVector pos = g4PhysVol.GetTranslation();
for (auto range : m_ranges) {
auto& [min, max] = range.second;
EAxis axis = EAxis(range.first);
matched = (pos[axis] >= min) && (pos[axis] <= max);
if (!matched) {
break;
}
}
return matched;
}
};

} // namespace Geant4PhysicalVolumeSelectors
} // namespace Acts
162 changes: 162 additions & 0 deletions Plugins/Geant4/include/Acts/Plugins/Geant4/Geant4SurfaceProvider.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// This file is part of the Acts project.
//
// Copyright (C) 2023 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 http://mozilla.org/MPL/2.0/.

#pragma once

#include "Acts/Detector/KdtSurfacesProvider.hpp"
#include "Acts/Detector/interface/ISurfacesProvider.hpp"
#include "Acts/Plugins/Geant4/Geant4DetectorSurfaceFactory.hpp"

#include "G4GDMLParser.hh"
#include "G4LogicalVolume.hh"
#include "G4VPhysicalVolume.hh"

namespace Acts {
namespace Experimental {

/// @brief A surface provider that extracts surfaces from a gdml file
///
/// This provider extracts volumes from a gdml file based on
/// the preselection criteria and converts them to surfaces.
/// By default, all the volumes are converted.
///
/// Optionally, it can be configured to return a range-based
/// subset of all the preselected surfaces. This is done
/// by setting the range and binning values in the kdtOptions
///
/// @note if the KDTree selection is not needed, the
/// template parameters can be left to their default values
/// as they will not affect the result.
///
/// @tparam kDim The number of dimensions for the KDTree
/// @tparam bSize The maximum number of surfaces per KDTree leaf
/// @tparam reference_generator The reference generator for the KDTree
template <std::size_t kDim = 2u, std::size_t bSize = 100u,
typename reference_generator =
detail::PolyhedronReferenceGenerator<1u, false>>
class Geant4SurfaceProvider : public Acts::Experimental::ISurfacesProvider {
public:
/// Nested configuration struct
struct Config {
/// The path of the gdml file
std::string gdmlPath = "";

/// Convert the length scale
ActsScalar scaleConversion = 1.;

/// Convert the material
bool convertMaterial = true;

/// Converted material thickness (< 0 indicates keeping original thickness)
ActsScalar convertedMaterialThickness = -1;

/// A selector for passive surfaces
std::shared_ptr<IGeant4PhysicalVolumeSelector> surfacePreselector =
std::make_shared<Acts::Geant4PhysicalVolumeSelectors::AllSelector>();
};

/// Optional configuration for the KDTree
struct kdtOptions {
/// A set of ranges to separate the surfaces
Acts::RangeXD<kDim, Acts::ActsScalar> range;

/// A set of binning values to perform the separation
std::array<Acts::BinningValue, kDim> binningValues;

/// The maximum number of surfaces per leaf
std::size_t leafSize = bSize;

/// The reference generator for the KDTree
reference_generator rgen;

/// Initialize range to be degenerate by default
kdtOptions() {
for (std::size_t i = 0; i < kDim; ++i) {
range[i].set(1, -1);
}
}
};

/// Constructor
///@param config The configuration struct
///@param options The optional configuration for KDTree
Geant4SurfaceProvider(const Config& config,
const kdtOptions& options = kdtOptions()) {
if (config.gdmlPath.empty()) {
throw std::invalid_argument(
"Geant4SurfaceProvider: no gdml file provided");
}
if (config.surfacePreselector == nullptr) {
throw std::invalid_argument(
"Geant4SurfaceProvider: no preselection criteria provided");
}

m_cfg = config;
m_kdtOptions = options;

/// Read the gdml file and get the world volume
G4GDMLParser parser;
parser.Read(m_cfg.gdmlPath);
m_g4World = parser.GetWorldVolume();

if (m_g4World == nullptr) {
throw std::invalid_argument(
"Geant4SurfaceProvider: No g4World initialized");
}
};

/// Destructor
~Geant4SurfaceProvider() override = default;

std::vector<std::shared_ptr<Acts::Surface>> surfaces(
[[maybe_unused]] const Acts::GeometryContext& gctx) const override {
/// Surface factory options
Acts::Geant4DetectorSurfaceFactory::Options g4SurfaceOptions;

/// Copy the configuration
/// This is done to avoid checking nullptrs
/// in the factory
g4SurfaceOptions.scaleConversion = m_cfg.scaleConversion;
g4SurfaceOptions.convertMaterial = m_cfg.convertMaterial;
g4SurfaceOptions.convertedMaterialThickness =
m_cfg.convertedMaterialThickness;
g4SurfaceOptions.passiveSurfaceSelector = m_cfg.surfacePreselector;

/// Generate the surface cache
Acts::Geant4DetectorSurfaceFactory::Cache g4SurfaceCache;
G4Transform3D g4ToWorld;

/// Find and store surfaces in the cache object
Acts::Geant4DetectorSurfaceFactory{}.construct(
g4SurfaceCache, g4ToWorld, *m_g4World, g4SurfaceOptions);

auto surfaces = g4SurfaceCache.passiveSurfaces;

/// If range is degenerate, return all surfaces
if (m_kdtOptions.range.degenerate()) {
return surfaces;
}

/// Otherwise, select the surfaces based on the range
auto kdtSurfaces =
Acts::Experimental::KdtSurfaces<kDim, bSize, reference_generator>(
gctx, surfaces, m_kdtOptions.binningValues);

return kdtSurfaces.surfaces(m_kdtOptions.range);
};

private:
Config m_cfg;

kdtOptions m_kdtOptions;

G4VPhysicalVolume* m_g4World = nullptr;
};

} // namespace Experimental
} // namespace Acts
1 change: 1 addition & 0 deletions Tests/UnitTests/Plugins/Geant4/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ add_unittest(Geant4Converters Geant4ConvertersTests.cpp)
add_unittest(Geant4DetectorElement Geant4DetectorElementTests.cpp)
add_unittest(Geant4PhysicalVolumeSelectors Geant4PhysicalVolumeSelectorsTests.cpp)
add_unittest(Geant4DetectorSurfaceFactory Geant4DetectorSurfaceFactoryTests.cpp)
add_unittest(Geant4SurfaceProvider Geant4SurfaceProviderTests.cpp)
Loading

0 comments on commit 16adb43

Please sign in to comment.