src/modules/GeometryBuilderGeant4/DetectorConstructionG4.cpp

Implements the Geant4 geometry construction process. More…

Detailed Description

Implements the Geant4 geometry construction process.

Copyright: Copyright (c) 2019-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 "DetectorConstructionG4.hpp"

#include <memory>
#include <string>
#include <utility>

#include <G4Box.hh>
#include <G4EllipticalTube.hh>
#include <G4IntersectionSolid.hh>
#include <G4LogicalVolume.hh>
#include <G4LogicalVolumeStore.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 <G4Trd.hh>
#include <G4Tubs.hh>
#include <G4UnionSolid.hh>
#include <G4UserLimits.hh>
#include <G4VSolid.hh>
#include <G4VisAttributes.hh>
#include "G4Material.hh"

#include "core/geometry/RadialStripDetectorModel.hpp"
#include "core/module/exceptions.h"
#include "core/utils/log.h"
#include "tools/ROOT.h"
#include "tools/geant4/geant4.h"

#include "GeometryConstructionG4.hpp"
#include "MaterialManager.hpp"
#include "Parameterization2DG4.hpp"

using namespace allpix;

DetectorConstructionG4::DetectorConstructionG4(GeometryManager* geo_manager) : geo_manager_(geo_manager) {}

void DetectorConstructionG4::build(const std::shared_ptr<G4LogicalVolume>& world_log) {

    // Get materials manager
    auto& materials = Materials::getInstance();

    /*
    Build the individual detectors
    */
    std::vector<std::shared_ptr<Detector>> detectors = geo_manager_->getDetectors();
    LOG(TRACE) << "Building " << detectors.size() << " device(s)";

    for(auto& detector : detectors) {
        // Material budget:
        double total_material_budget = 0;

        // Get pointer to the model of the detector
        auto model = detector->getModel();

        std::string name = detector->getName();
        LOG(DEBUG) << "Creating Geant4 model for " << name;
        LOG(DEBUG) << " Wrapper dimensions of model: " << Units::display(model->getSize(), {"mm", "um"});
        LOG(TRACE) << " Sensor dimensions: " << Units::display(model->getSensorSize(), {"mm", "um"});
        LOG(TRACE) << " Chip dimensions: " << Units::display(model->getChipSize(), {"mm", "um"});
        LOG(DEBUG) << " Global position and orientation of the detector:";

        // Build a radial wrapper if radial_strip model is used, otherwise build a box wrapper
        auto radial_model = std::dynamic_pointer_cast<RadialStripDetectorModel>(model);
        if(radial_model != nullptr) {
            // Create the base cylindrical section; wider than the requested dimensions to account for the stereo angle
            auto* wrapper_base_tub = new G4Tubs("wrapper_base" + name,
                                                radial_model->getRowRadius(0),
                                                radial_model->getRowRadius(radial_model->getNPixels().y()),
                                                radial_model->getSize().z() / 2,
                                                90 * CLHEP::deg - radial_model->getRowAngleMax() / 2 * 1.5,
                                                radial_model->getRowAngleMax() * 1.5);

            // Create the angled cylindrical section coming from the focal point; longer than the requested dimensions to
            // account for the stereo angle
            auto* wrapper_angled_tub = new G4Tubs("wrapper_angled" + name,
                                                  radial_model->getRowRadius(0) * 0.95,
                                                  radial_model->getRowRadius(radial_model->getNPixels().y()) * 1.05,
                                                  radial_model->getSize().z() / 2,
                                                  90.0 * CLHEP::deg - radial_model->getRowAngleMax() / 2,
                                                  radial_model->getRowAngleMax());

            // Get the requested stereo angle
            auto stereo_angle = radial_model->getStereoAngle();
            LOG(TRACE) << "Applying stereo angle of " << Units::display(stereo_angle, "mrad");

            // Transformation for the angled cylindrical section
            auto angled_tub_rot = G4RotationMatrix();
            angled_tub_rot.rotateZ(stereo_angle);
            auto center_radius = radial_model->getCenterRadius();
            auto angled_tub_pos =
                G4ThreeVector(center_radius * sin(stereo_angle), -center_radius * (1 - cos(stereo_angle)), 0);
            auto angled_tub_trf = G4Transform3D(angled_tub_rot, angled_tub_pos);
            auto wrapper_final_tub = make_shared_no_delete<G4IntersectionSolid>(
                "wrapper_" + name, wrapper_base_tub, wrapper_angled_tub, angled_tub_trf);
            solids_.push_back(wrapper_final_tub);
        } else {
            // Create the wrapper box
            auto wrapper_box = make_shared_no_delete<G4Box>(
                "wrapper_" + name, model->getSize().x() / 2.0, model->getSize().y() / 2.0, model->getSize().z() / 2.0);
            solids_.push_back(wrapper_box);
        }

        // Create the wrapper logical volume
        auto wrapper_log = make_shared_no_delete<G4LogicalVolume>(
            solids_.back().get(), materials.get("world_material"), "wrapper_" + name + "_log");
        geo_manager_->setExternalObject(name, "wrapper_log", wrapper_log);

        // Get position and orientation
        auto position = detector->getPosition();
        LOG(DEBUG) << " - Position\t\t:\t" << Units::display(position, {"mm", "um"});
        ROOT::Math::Rotation3D orientation = detector->getOrientation();
        std::vector<double> copy_vec(9);
        orientation.GetComponents(copy_vec.begin(), copy_vec.end());
        ROOT::Math::XYZPoint vx, vy, vz;
        orientation.GetComponents(vx, vy, vz);
        auto rotWrapper = std::make_shared<G4RotationMatrix>(copy_vec.data());

        // Additional translation for models whose coordinate center is not the volume center
        auto model_translation = std::make_shared<G4ThreeVector>();
        if(radial_model != nullptr) {
            *model_translation += G4ThreeVector(0, radial_model->getCenterRadius(), 0);
        }
        geo_manager_->setExternalObject(name, "model_translation", model_translation);

        // Build full transformation
        auto wrapperGeoTranslation = toG4Vector(model->getMatrixCenter() - model->getModelCenter());
        wrapperGeoTranslation += *model_translation;
        wrapperGeoTranslation *= *rotWrapper;
        G4ThreeVector posWrapper = toG4Vector(position) - wrapperGeoTranslation;
        geo_manager_->setExternalObject(name, "rotation_matrix", rotWrapper);
        G4Transform3D transform_phys(*rotWrapper, posWrapper);

        G4LogicalVolumeStore* log_volume_store = G4LogicalVolumeStore::GetInstance();
        G4LogicalVolume* world_log_volume = log_volume_store->GetVolume("world_log");

        if(world_log_volume == nullptr) {
            throw ModuleError("Cannot find world volume");
        }

        // Place the wrapper
        auto wrapper_phys = make_shared_no_delete<G4PVPlacement>(
            transform_phys, wrapper_log.get(), "wrapper_" + name + "_phys", world_log.get(), false, 0, true);
        geo_manager_->setExternalObject(name, "wrapper_phys", wrapper_phys);

        LOG(DEBUG) << " Center of the geometry parts relative to the detector wrapper geometric center:";

        // Get sensor material
        auto sensor_material_name = allpix::to_string(model->getSensorMaterial());
        auto* sensor_material = materials.get(sensor_material_name);
        LOG(DEBUG) << " - Sensor material\t\t:\t" << sensor_material_name;

        // Build a radial sensor box if radial_strip model is used, otherwise build a rectangular box
        if(radial_model != nullptr) {
            // Create the base cylindrical section wider than the requested dimensions to account for the stereo angle
            auto* sensor_base_tub = new G4Tubs("sensor_base" + name,
                                               radial_model->getRowRadius(0),
                                               radial_model->getRowRadius(radial_model->getNPixels().y()),
                                               radial_model->getSize().z() / 2,
                                               90 * CLHEP::deg - radial_model->getRowAngleMax() / 2 * 1.5,
                                               radial_model->getRowAngleMax() * 1.5);

            // Create the angled cylindrical section coming from the focal point
            auto* sensor_angled_tub = new G4Tubs("sensor_angled" + name,
                                                 radial_model->getRowRadius(0) * 0.95,
                                                 radial_model->getRowRadius(radial_model->getNPixels().y()) * 1.05,
                                                 radial_model->getSize().z() / 2,
                                                 90.0 * CLHEP::deg - radial_model->getRowAngleMax() / 2,
                                                 radial_model->getRowAngleMax());

            // Get requested stereo angle
            auto stereo_angle = radial_model->getStereoAngle();

            // Transformation for the angled cylindrical section
            auto angled_tub_rot = G4RotationMatrix();
            angled_tub_rot.rotateZ(stereo_angle);
            auto center_radius = radial_model->getCenterRadius();
            auto angled_tub_pos =
                G4ThreeVector(center_radius * sin(stereo_angle), -center_radius * (1 - cos(stereo_angle)), 0);
            auto angled_tub_trf = G4Transform3D(angled_tub_rot, angled_tub_pos);
            auto sensor_final_tub = make_shared_no_delete<G4IntersectionSolid>(
                "wrapper_" + name, sensor_base_tub, sensor_angled_tub, angled_tub_trf);
            solids_.push_back(sensor_final_tub);
        } else {
            auto sensor_box = make_shared_no_delete<G4Box>("sensor_" + name,
                                                           model->getSensorSize().x() / 2.0,
                                                           model->getSensorSize().y() / 2.0,
                                                           model->getSensorSize().z() / 2.0);
            solids_.push_back(sensor_box);
        }

        // Create the sensor logical volume
        auto sensor_log =
            make_shared_no_delete<G4LogicalVolume>(solids_.back().get(), sensor_material, "sensor_" + name + "_log");
        geo_manager_->setExternalObject(name, "sensor_log", sensor_log);

        // Add sensor material to total material budget:
        total_material_budget += (model->getSensorSize().z() / sensor_log->GetMaterial()->GetRadlen());

        // Place the sensor box
        auto sensor_pos = toG4Vector(model->getSensorCenter() - model->getModelCenter());
        LOG(DEBUG) << "  - Sensor\t\t:\t" << Units::display(sensor_pos, {"mm", "um"});
        auto sensor_phys = make_shared_no_delete<G4PVPlacement>(
            nullptr, sensor_pos, sensor_log.get(), "sensor_" + name + "_phys", wrapper_log.get(), false, 0, true);
        geo_manager_->setExternalObject(name, "sensor_phys", sensor_phys);

        // Create the pixel box and logical volume
        auto pixel_box = make_shared_no_delete<G4Box>("pixel_" + name,
                                                      model->getPixelSize().x() / 2.0,
                                                      model->getPixelSize().y() / 2.0,
                                                      model->getSensorSize().z() / 2.0);
        solids_.push_back(pixel_box);
        auto pixel_log = make_shared_no_delete<G4LogicalVolume>(pixel_box.get(), sensor_material, "pixel_" + name + "_log");
        geo_manager_->setExternalObject(name, "pixel_log", pixel_log);

        // Create the parameterization for the pixel grid
        std::shared_ptr<G4VPVParameterisation> pixel_param =
            std::make_shared<Parameterization2DG4>(model->getNPixels().x(),
                                                   model->getPixelSize().x(),
                                                   model->getPixelSize().y(),
                                                   -model->getMatrixSize().x() / 2.0,
                                                   -model->getMatrixSize().y() / 2.0,
                                                   0);
        geo_manager_->setExternalObject(name, "pixel_param", std::move(pixel_param));
        // WARNING: do not place the actual parameterization, only use it if we need it

        // Construct the chips only if necessary
        if(model->getChipSize().z() > 1e-9) {
            // Create the chip box
            auto chip_box = make_shared_no_delete<G4Box>("chip_" + name,
                                                         model->getChipSize().x() / 2.0,
                                                         model->getChipSize().y() / 2.0,
                                                         model->getChipSize().z() / 2.0);
            solids_.push_back(chip_box);

            // Create the logical volume for the chip
            auto chip_log =
                make_shared_no_delete<G4LogicalVolume>(chip_box.get(), materials.get("silicon"), "chip_" + name + "_log");
            geo_manager_->setExternalObject(name, "chip_log", chip_log);

            // Add chip material to total material budget:
            total_material_budget += (model->getChipSize().z() / chip_log->GetMaterial()->GetRadlen());

            // Place the chip
            auto chip_pos = toG4Vector(model->getChipCenter() - model->getModelCenter());
            LOG(DEBUG) << "  - Chip\t\t:\t" << Units::display(chip_pos, {"mm", "um"});
            auto chip_phys = make_shared_no_delete<G4PVPlacement>(
                nullptr, chip_pos, chip_log.get(), "chip_" + name + "_phys", wrapper_log.get(), false, 0, true);
            geo_manager_->setExternalObject(name, "chip_phys", chip_phys);
        }

        /*
         * SUPPORT
         * optional layers of support
         */
        auto supports_log = std::make_shared<std::vector<std::shared_ptr<G4LogicalVolume>>>();
        auto supports_phys = std::make_shared<std::vector<std::shared_ptr<G4PVPlacement>>>();
        int support_idx = 0;
        for(auto& layer : model->getSupportLayers()) {
            // Create the box containing the support
            auto support_box = make_shared_no_delete<G4Box>("support_" + name + "_" + std::to_string(support_idx),
                                                            layer.getSize().x() / 2.0,
                                                            layer.getSize().y() / 2.0,
                                                            layer.getSize().z() / 2.0);
            solids_.push_back(support_box);

            std::shared_ptr<G4VSolid> support_solid = support_box;
            if(layer.hasHole()) {
                // NOTE: Double the hole size in the z-direction to ensure no fake surfaces are created
                std::shared_ptr<G4VSolid> hole_solid;

                if(layer.getHoleType() == "cylinder") {
                    hole_solid =
                        make_shared_no_delete<G4EllipticalTube>("support_" + name + "_hole_" + std::to_string(support_idx),
                                                                layer.getHoleSize().x() / 2.0,
                                                                layer.getHoleSize().y() / 2.0,
                                                                layer.getHoleSize().z());
                } else {
                    hole_solid = make_shared_no_delete<G4Box>("support_" + name + "_hole_" + std::to_string(support_idx),
                                                              layer.getHoleSize().x() / 2.0,
                                                              layer.getHoleSize().y() / 2.0,
                                                              layer.getHoleSize().z());
                }

                solids_.push_back(hole_solid);

                G4Transform3D transform(G4RotationMatrix(), toG4Vector(layer.getHoleCenter() - layer.getCenter()));
                auto subtraction_solid = make_shared_no_delete<G4SubtractionSolid>("support_" + name + "_subtraction_" +
                                                                                       std::to_string(support_idx),
                                                                                   support_box.get(),
                                                                                   hole_solid.get(),
                                                                                   transform);
                solids_.push_back(subtraction_solid);
                support_solid = subtraction_solid;
            }

            // Create the logical volume for the support
            G4Material* support_material = nullptr;
            try {
                support_material = materials.get(layer.getMaterial());
            } catch(ModuleError& e) {
                throw ModuleError("Cannot construct support layer: " + std::string(e.what()));
            }

            auto support_log = make_shared_no_delete<G4LogicalVolume>(
                support_solid.get(), support_material, "support_" + name + "_log_" + std::to_string(support_idx));
            supports_log->push_back(support_log);

            // Add support layer material to total material budget if it doesn't have a hole:
            // WARNING: this of course does not take into account where exactly the hole is and how big it is...
            if(!layer.hasHole()) {
                total_material_budget += (layer.getSize().z() / support_log->GetMaterial()->GetRadlen());
            }

            // Place the support
            auto support_pos = toG4Vector(layer.getCenter() - model->getModelCenter());
            LOG(DEBUG) << "  - Support\t\t:\t" << Units::display(support_pos, {"mm", "um"});
            auto support_phys =
                make_shared_no_delete<G4PVPlacement>(nullptr,
                                                     support_pos,
                                                     support_log.get(),
                                                     "support_" + name + "_phys_" + std::to_string(support_idx),
                                                     wrapper_log.get(),
                                                     false,
                                                     0,
                                                     true);
            supports_phys->push_back(support_phys);

            ++support_idx;
        }
        geo_manager_->setExternalObject(name, "supports_log", supports_log);
        geo_manager_->setExternalObject(name, "supports_phys", supports_phys);

        // Build the bump bonds only for hybrid pixel detectors
        auto hybrid_chip = std::dynamic_pointer_cast<HybridAssembly>(model->getAssembly());
        if(hybrid_chip != nullptr) {

            // Get parameters from model
            auto bump_height = hybrid_chip->getBumpHeight();
            auto bump_sphere_radius = hybrid_chip->getBumpSphereRadius();
            auto bump_cylinder_radius = hybrid_chip->getBumpCylinderRadius();

            // Create the volume containing the bumps
            auto bump_box = make_shared_no_delete<G4Box>(
                "bump_box_" + name, model->getSensorSize().x() / 2.0, model->getSensorSize().y() / 2.0, bump_height / 2.);
            solids_.push_back(bump_box);

            // Create the logical wrapper volume
            auto bumps_wrapper_log = make_shared_no_delete<G4LogicalVolume>(
                bump_box.get(), materials.get("world_material"), "bumps_wrapper_" + name + "_log");
            geo_manager_->setExternalObject(name, "bumps_wrapper_log", bumps_wrapper_log);

            // Place the general bumps volume
            G4ThreeVector bumps_pos =
                toG4Vector(hybrid_chip->getBumpsOffset() +
                           ROOT::Math::XYZVector(0, 0, model->getSensorSize().z() / 2.0 - model->getModelCenter().z()));
            LOG(DEBUG) << "  - Bumps\t\t:\t" << Units::display(bumps_pos, {"mm", "um"});
            auto bumps_wrapper_phys = make_shared_no_delete<G4PVPlacement>(nullptr,
                                                                           bumps_pos,
                                                                           bumps_wrapper_log.get(),
                                                                           "bumps_wrapper_" + name + "_phys",
                                                                           wrapper_log.get(),
                                                                           false,
                                                                           0,
                                                                           true);
            geo_manager_->setExternalObject(name, "bumps_wrapper_phys", bumps_wrapper_phys);

            // Create the individual bump solid
            auto bump_sphere = make_shared_no_delete<G4Sphere>(
                "bumps_" + name + "_sphere", 0, bump_sphere_radius, 0, 360 * CLHEP::deg, 0, 360 * CLHEP::deg);
            solids_.push_back(bump_sphere);
            auto bump_tube = make_shared_no_delete<G4Tubs>(
                "bumps_" + name + "_tube", 0., bump_cylinder_radius, bump_height / 2., 0., 360 * CLHEP::deg);
            solids_.push_back(bump_tube);
            auto bump = make_shared_no_delete<G4UnionSolid>("bumps_" + name, bump_sphere.get(), bump_tube.get());
            solids_.push_back(bump);

            // Create the logical volume for the individual bumps
            auto bumps_cell_log =
                make_shared_no_delete<G4LogicalVolume>(bump.get(), materials.get("solder"), "bumps_" + name + "_log");
            geo_manager_->setExternalObject(name, "bumps_cell_log", bumps_cell_log);

            // Add bump material equivalent to uniform solder layer to total material budget:
            auto radius = std::max(hybrid_chip->getBumpSphereRadius(), hybrid_chip->getBumpCylinderRadius());
            auto relativeArea = M_PI * radius * radius / model->getPixelSize().x() / model->getPixelSize().y();
            total_material_budget +=
                (relativeArea * hybrid_chip->getBumpHeight() / bumps_cell_log->GetMaterial()->GetRadlen());

            // Place the bump bonds grid
            std::shared_ptr<G4VPVParameterisation> bumps_param = std::make_shared<Parameterization2DG4>(
                model->getNPixels().x(),
                model->getPixelSize().x(),
                model->getPixelSize().y(),
                -(model->getNPixels().x() * model->getPixelSize().x()) / 2.0 + (hybrid_chip->getBumpsOffset().x()),
                -(model->getNPixels().y() * model->getPixelSize().y()) / 2.0 + (hybrid_chip->getBumpsOffset().y()),
                0);
            geo_manager_->setExternalObject(name, "bumps_param", bumps_param);

            std::shared_ptr<G4PVParameterised> bumps_param_phys =
                std::make_shared<ParameterisedG4>("bumps_" + name + "_phys",
                                                  bumps_cell_log.get(),
                                                  bumps_wrapper_log.get(),
                                                  kUndefined,
                                                  model->getNPixels().x() * model->getNPixels().y(),
                                                  bumps_param.get(),
                                                  false);
            geo_manager_->setExternalObject(name, "bumps_param_phys", std::move(bumps_param_phys));
        }

        // Store the total material budget:
        LOG(DEBUG) << "Storing total material budget of " << total_material_budget << " x/X0 for detector " << name;
        geo_manager_->setExternalObject(name, "material_budget", std::make_shared<double>(total_material_budget));

        LOG(TRACE) << " Constructed detector " << detector->getName() << " successfully";
    }
}

Updated on 2024-12-13 at 08:31:37 +0000