src/modules/GeometryBuilderGeant4/GeometryConstructionG4.cpp
Implements the Geant4 geometry construction process. More…
Detailed Description
Implements the Geant4 geometry construction process.
Copyright: Copyright (c) 2017-2024 CERN and the Allpix Squared authors. This software is distributed under the terms of the MIT License, copied verbatim in the file “LICENSE.md”. In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an Intergovernmental Organization or submit itself to any jurisdiction. SPDX-License-Identifier: MIT
Remark: Code is based on code from Mathieu Benoit
Source code
#include "GeometryConstructionG4.hpp"
#include "MaterialManager.hpp"
#include <memory>
#include <string>
#include <utility>
#include <G4Box.hh>
#include <G4LogicalVolume.hh>
#include <G4NavigationHistory.hh>
#include <G4NistManager.hh>
#include <G4PVDivision.hh>
#include <G4PVPlacement.hh>
#include <G4PhysicalVolumeStore.hh>
#include <G4Sphere.hh>
#include <G4StepLimiterPhysics.hh>
#include <G4SubtractionSolid.hh>
#include <G4ThreeVector.hh>
#include <G4Tubs.hh>
#include <G4UnionSolid.hh>
#include <G4UserLimits.hh>
#include <G4VSolid.hh>
#include <G4VisAttributes.hh>
#include "core/module/exceptions.h"
#include "core/utils/log.h"
#include "tools/ROOT.h"
#include "tools/geant4/G4LoggingDestination.hpp"
#include "tools/geant4/geant4.h"
#include "Parameterization2DG4.hpp"
using namespace allpix;
GeometryConstructionG4::GeometryConstructionG4(GeometryManager* geo_manager, Configuration& config)
: geo_manager_(geo_manager), config_(config) {
detector_builder_ = std::make_unique<DetectorConstructionG4>(geo_manager_);
passive_builder_ = std::make_unique<PassiveMaterialConstructionG4>(geo_manager_);
passive_builder_->registerVolumes();
}
G4VPhysicalVolume* GeometryConstructionG4::Construct() {
// Initialize materials
auto& materials = Materials::getInstance();
// Set world material
G4Material* g4material_world = nullptr;
auto world_material = config_.get<std::string>("world_material", "air");
try {
g4material_world = materials.get(world_material);
} catch(ModuleError& e) {
throw InvalidValueError(config_, "world_material", e.what());
}
// Register the world material for others as reference:
materials.set("world_material", g4material_world);
LOG(TRACE) << "Material of world is " << g4material_world->GetName();
// Calculate world size
ROOT::Math::XYZVector half_world_size;
ROOT::Math::XYZPoint min_coord = geo_manager_->getMinimumCoordinate();
ROOT::Math::XYZPoint max_coord = geo_manager_->getMaximumCoordinate();
half_world_size.SetX(std::max(std::abs(min_coord.x()), std::abs(max_coord.x())));
half_world_size.SetY(std::max(std::abs(min_coord.y()), std::abs(max_coord.y())));
half_world_size.SetZ(std::max(std::abs(min_coord.z()), std::abs(max_coord.z())));
// Calculate and apply margins to world size
auto margin_percentage = config_.get<double>("world_margin_percentage", 0.1);
auto minimum_margin = config_.get<ROOT::Math::XYZPoint>("world_minimum_margin", {0, 0, 0});
double add_x = half_world_size.x() * margin_percentage;
if(add_x < minimum_margin.x()) {
add_x = minimum_margin.x();
}
double add_y = half_world_size.y() * margin_percentage;
if(add_y < minimum_margin.y()) {
add_y = minimum_margin.y();
}
double add_z = half_world_size.z() * margin_percentage;
if(add_z < minimum_margin.z()) {
add_z = minimum_margin.z();
}
half_world_size.SetX(half_world_size.x() + add_x);
half_world_size.SetY(half_world_size.y() + add_y);
half_world_size.SetZ(half_world_size.z() + add_z);
LOG(DEBUG) << "World size is " << Units::display(2 * half_world_size, {"mm"});
// Build the world
auto world_box = std::make_shared<G4Box>("World", half_world_size.x(), half_world_size.y(), half_world_size.z());
solids_.push_back(world_box);
world_log_ =
std::make_shared<G4LogicalVolume>(world_box.get(), g4material_world, "world_log", nullptr, nullptr, nullptr);
// Set the world to invisible in the viewer
world_log_->SetVisAttributes(G4VisAttributes::GetInvisible());
geo_manager_->setExternalObject("", "world_log", world_log_);
// Place the world at the center
world_phys_ =
std::make_unique<G4PVPlacement>(nullptr, G4ThreeVector(0., 0., 0.), world_log_.get(), "World", nullptr, false, 0);
// Build all the geometries that have been added to the GeometryBuilder vector, including Detectors and Target
passive_builder_->buildVolumes(world_log_);
detector_builder_->build(world_log_);
// Check for overlaps:
check_overlaps();
// Verify transformations:
verify_transforms();
return world_phys_.get();
}
void GeometryConstructionG4::check_overlaps() const {
G4PhysicalVolumeStore* phys_volume_store = G4PhysicalVolumeStore::GetInstance();
LOG(TRACE) << "Checking overlaps";
bool overlapFlag = false;
auto current_level = G4LoggingDestination::getG4coutReportingLevel();
G4LoggingDestination::setG4coutReportingLevel(LogLevel::ERROR);
for(auto* volume : (*phys_volume_store)) {
overlapFlag = volume->CheckOverlaps(1000, 0., false) || overlapFlag;
}
G4LoggingDestination::setG4coutReportingLevel(current_level);
if(overlapFlag) {
LOG(ERROR) << "Overlapping volumes detected.";
} else {
LOG(INFO) << "No overlapping volumes detected.";
}
}
// Verify that coordinate transformations are performed properly
void GeometryConstructionG4::verify_transforms() const {
// Navigation history to traverse the geometry
auto tree = std::make_unique<G4NavigationHistory>();
tree->SetFirstEntry(world_phys_.get());
// Lambda to locate physical volume in the world geometry and to retrieve its transformation with respect to the world
std::function<G4AffineTransform(const G4VPhysicalVolume*)> get_world_transform;
get_world_transform = [&tree, &get_world_transform](const G4VPhysicalVolume* volume) -> G4AffineTransform {
if(tree->GetTopVolume() == volume) {
auto transform = tree->GetTopTransform();
tree->Reset();
return transform;
}
// Loop through children and check where we belong
auto* current = tree->GetTopVolume()->GetLogicalVolume();
for(size_t i = 0; i < current->GetNoDaughters(); ++i) {
auto* daughter = current->GetDaughter(i);
if(daughter == volume || daughter->GetLogicalVolume()->IsAncestor(volume)) {
tree->NewLevel(daughter);
return get_world_transform(volume);
}
}
assert("Missing physical volume" && false);
return {};
};
// A test vector
const G4ThreeVector global = {1., 1., 1.};
// Calculate transformations for all detectors:
for(auto& detector : geo_manager_->getDetectors()) {
auto local = detector->getLocalPosition(static_cast<ROOT::Math::XYZPoint>(global));
// Obtain physical sensor volume, its transformation to the world volume and apply to global test vector:
auto sensor = geo_manager_->getExternalObject<G4PVPlacement>(detector->getName(), "sensor_phys");
auto coord_g4 = get_world_transform(sensor.get()).TransformPoint(global);
// Apply translation to correct for volume origin not corresponding to volume center
coord_g4 -= *geo_manager_->getExternalObject<G4ThreeVector>(detector->getName(), "model_translation");
// Calculate local coordinates by correcting for sensor offsets etc
auto local_g4 = static_cast<ROOT::Math::XYZVector>(coord_g4) + detector->getModel()->getSensorCenter();
if((local_g4 - local).mag2() > 0.001) {
LOG(FATAL) << "Model \"" << detector->getModel()->getType() << "\" has invalid coordinate transformation";
LOG(FATAL) << "Coordinate transformation test for detector " << detector->getName() << std::endl
<< "Global test vector: " << Units::display(global, {"mm", "um"}) << std::endl
<< "In local coordinates: " << Units::display(local, {"mm", "um"}) << std::endl
<< "In G4 local coordinates: " << Units::display(local_g4, {"mm", "um"});
assert("Invalid coordinate transformation" && false);
} else {
LOG(TRACE) << "Completed coordinate transformation test for detector " << detector->getName() << std::endl
<< "Global test vector: " << Units::display(global, {"mm", "um"}) << std::endl
<< "In local coordinates: " << Units::display(local, {"mm", "um"}) << std::endl
<< "In G4 local coordinates: " << Units::display(local_g4, {"mm", "um"});
}
}
}
Updated on 2025-02-27 at 14:14:46 +0000