src/core/geometry/GeometryManager.cpp

Implementation of geometry manager. More…

Detailed Description

Implementation of geometry manager.

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

Source code


#include <fstream>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <Math/RotationX.h>
#include <Math/RotationY.h>
#include <Math/RotationZ.h>
#include <Math/RotationZYX.h>
#include <Math/Vector3D.h>

#include "GeometryManager.hpp"
#include "core/config/ConfigReader.hpp"
#include "core/geometry/exceptions.h"
#include "core/module/exceptions.h"
#include "core/utils/distributions.h"
#include "core/utils/log.h"
#include "core/utils/unit.h"
#include "tools/ROOT.h"

using namespace allpix;
using namespace ROOT::Math;

GeometryManager::GeometryManager() : closed_{false} {}

void GeometryManager::load(ConfigManager* conf_manager, RandomNumberGenerator& seeder) {
    // Set up a random number generator and seed it with the global seed:
    random_generator_.seed(seeder());

    // Loop over all defined detectors
    LOG(DEBUG) << "Loading detectors";
    // Gets a list of detector configurations. The sections for each detector in the geometry config file.
    for(auto& geometry_section : conf_manager->getDetectorConfigurations()) {

        // Read role of this section and default to "active" (i.e. detector)
        auto role = geometry_section.get<std::string>("role", "active");
        std::transform(role.begin(), role.end(), role.begin(), ::tolower);
        if(role == "passive") {
            // Check for duplicate names:
            auto it = std::find_if(
                passive_elements_.begin(),
                passive_elements_.end(),
                [name = geometry_section.getName()](const Configuration& cfg) { return name == cfg.getName(); });
            if(it != passive_elements_.end()) {
                throw PassiveElementExistsError(geometry_section.getName());
            }

            // Calculate the orientations of passive elements
            passive_orientations_[geometry_section.getName()] = calculate_orientation(geometry_section);

            // Check material unless it's a GDML file placement
            auto type = geometry_section.get<std::string>("type");
            std::transform(type.begin(), type.end(), type.begin(), ::tolower);

            passive_elements_.push_back(geometry_section);
            LOG(DEBUG) << "Passive element " << geometry_section.getName() << ", putting aside";
            continue;

        } else if(role != "active") {
            throw InvalidValueError(geometry_section, "role", "unknown role");
        }

        LOG(DEBUG) << "Detector " << geometry_section.getName() << ":";
        // Get the position and orientation of the detector
        auto [position, orientation] = calculate_orientation(geometry_section);

        // Create the detector and add it without model
        // NOTE: cannot use make_shared here due to the private constructor
        auto detector =
            std::shared_ptr<Detector>(new Detector(geometry_section.getName(), std::move(position), orientation));
        addDetector(detector);

        // Add a link to the detector to add the model later
        nonresolved_models_[geometry_section.get<std::string>("type")].emplace_back(geometry_section, detector.get());
    }

    // Load the list of standard model paths
    const Configuration& global_config = conf_manager->getGlobalConfiguration();
    if(global_config.has("model_paths")) {
        auto extra_paths = global_config.getPathArray("model_paths", true);
        model_paths_.insert(model_paths_.end(), extra_paths.begin(), extra_paths.end());
        LOG(TRACE) << "Registered model paths from configuration.";
    }
    if(std::filesystem::is_directory(ALLPIX_MODEL_DIRECTORY)) {
        model_paths_.emplace_back(ALLPIX_MODEL_DIRECTORY);
        LOG(TRACE) << "Registered model path: " << ALLPIX_MODEL_DIRECTORY;
    }
    const char* data_dirs_env = std::getenv("XDG_DATA_DIRS");
    if(data_dirs_env == nullptr || strlen(data_dirs_env) == 0) {
        data_dirs_env = "/usr/local/share/:/usr/share/:";
    }
    auto data_dirs = split<std::filesystem::path>(data_dirs_env, ":");
    for(auto data_dir : data_dirs) {
        data_dir /= std::filesystem::path(ALLPIX_PROJECT_NAME) / std::string("models");
        if(std::filesystem::is_directory(data_dir)) {
            model_paths_.emplace_back(data_dir);
            LOG(TRACE) << "Registered global model path: " << data_dir;
        }
    }
    auto config_file_path = global_config.getFilePath();
    if(!config_file_path.empty() && std::filesystem::is_directory(config_file_path.parent_path())) {
        model_paths_.emplace_back(config_file_path.parent_path());
        LOG(TRACE) << "Registered path of configuration file as model location.";
    }
}

std::pair<XYZPoint, Rotation3D> GeometryManager::getPassiveElementOrientation(const std::string& passive_element) const {
    if(passive_orientations_.count(passive_element) == 0) {
        throw ModuleError("Passive Material '" + passive_element + "' is not defined.");
    }
    return passive_orientations_.at(passive_element);
}

ROOT::Math::XYZPoint GeometryManager::getMinimumCoordinate() {
    if(!closed_) {
        close_geometry();
    }

    ROOT::Math::XYZPoint min_point(0, 0, 0);
    // Loop through all detector
    for(auto& detector : detectors_) {
        // Get the model of the detector
        auto model = detector->getModel();

        std::array<int, 8> offset_x = {{1, 1, 1, 1, -1, -1, -1, -1}};
        std::array<int, 8> offset_y = {{1, 1, -1, -1, 1, 1, -1, -1}};
        std::array<int, 8> offset_z = {{1, -1, 1, -1, 1, -1, 1, -1}};

        for(size_t i = 0; i < 8; ++i) {
            auto point = model->getModelCenter();
            point.SetX(point.x() + offset_x.at(i) * model->getSize().x() / 2.0);
            point.SetY(point.y() + offset_y.at(i) * model->getSize().y() / 2.0);
            point.SetZ(point.z() + offset_z.at(i) * model->getSize().z() / 2.0);
            point = detector->getGlobalPosition(point);

            min_point.SetX(std::min(min_point.x(), point.x()));
            min_point.SetY(std::min(min_point.y(), point.y()));
            min_point.SetZ(std::min(min_point.z(), point.z()));
        }
    }

    // Loop through all separate points
    for(auto& point : points_) {
        min_point.SetX(std::min(min_point.x(), point.x()));
        min_point.SetY(std::min(min_point.y(), point.y()));
        min_point.SetZ(std::min(min_point.z(), point.z()));
    }

    return min_point;
}

ROOT::Math::XYZPoint GeometryManager::getMaximumCoordinate() {
    if(!closed_) {
        close_geometry();
    }

    ROOT::Math::XYZPoint max_point(0, 0, 0);
    // Loop through all detector
    for(auto& detector : detectors_) {
        // Get the model of the detector
        auto model = detector->getModel();

        std::array<int, 8> offset_x = {{1, 1, 1, 1, -1, -1, -1, -1}};
        std::array<int, 8> offset_y = {{1, 1, -1, -1, 1, 1, -1, -1}};
        std::array<int, 8> offset_z = {{1, -1, 1, -1, 1, -1, 1, -1}};

        for(size_t i = 0; i < 8; ++i) {
            auto point = model->getModelCenter();
            point.SetX(point.x() + offset_x.at(i) * model->getSize().x() / 2.0);
            point.SetY(point.y() + offset_y.at(i) * model->getSize().y() / 2.0);
            point.SetZ(point.z() + offset_z.at(i) * model->getSize().z() / 2.0);
            point = detector->getGlobalPosition(point);
            max_point.SetX(std::max(max_point.x(), point.x()));
            max_point.SetY(std::max(max_point.y(), point.y()));
            max_point.SetZ(std::max(max_point.z(), point.z()));
        }
    }

    // Loop through all separate points
    for(auto& point : points_) {
        max_point.SetX(std::max(max_point.x(), point.x()));
        max_point.SetY(std::max(max_point.y(), point.y()));
        max_point.SetZ(std::max(max_point.z(), point.z()));
    }

    return max_point;
}

void GeometryManager::addPoint(ROOT::Math::XYZPoint point) {
    if(closed_) {
        throw ModuleError("Geometry is already closed before adding detector");
    }
    points_.push_back(std::move(point));
}

void GeometryManager::addModel(std::shared_ptr<DetectorModel> model) {
    if(closed_) {
        throw ModuleError("Geometry is already closed before adding detector");
    }
    if(model == nullptr) {
        throw InvalidModuleActionException("Added model cannot be a null pointer");
    }

    LOG(TRACE) << "Registering new model " << model->getType();
    if(model_names_.find(model->getType()) != model_names_.end()) {
        throw DetectorModelExistsError(model->getType());
    }

    model_names_.insert(model->getType());
    models_.push_back(std::move(model));
}

bool GeometryManager::needsModel(const std::string& name) const {
    return nonresolved_models_.find(name) != nonresolved_models_.end();
}
bool GeometryManager::hasModel(const std::string& name) const { return model_names_.find(name) != model_names_.end(); }

std::shared_ptr<DetectorModel> GeometryManager::getModel(const std::string& name) const {
    // FIXME: this is not a very nice way to implement this
    for(const auto& model : models_) {
        if(model->getType() == name) {
            return model;
        }
    }
    throw allpix::InvalidDetectorModelError(name);
}

void GeometryManager::addDetector(std::shared_ptr<Detector> detector) {
    if(closed_) {
        throw ModuleError("Geometry is already closed before adding detector");
    }
    if(detector == nullptr) {
        throw InvalidModuleActionException("Added detector cannot be a null pointer");
    }

    LOG(TRACE) << "Registering new detector " << detector->getName();

    // The name global is used for objects not assigned to any detector, hence it shouldn't be used as a detector name
    if(detector->getName() == "global") {
        throw DetectorInvalidNameError(detector->getName());
    }

    if(detector_names_.find(detector->getName()) != detector_names_.end()) {
        throw DetectorExistsError(detector->getName());
    }

    detector_names_.insert(detector->getName());
    detectors_.push_back(std::move(detector));
}

bool GeometryManager::hasDetector(const std::string& name) const {
    return detector_names_.find(name) != detector_names_.end();
}

std::vector<std::shared_ptr<Detector>> GeometryManager::getDetectors() {
    if(!closed_) {
        close_geometry();
    }

    return detectors_;
}
std::shared_ptr<Detector> GeometryManager::getDetector(const std::string& name) {
    if(!closed_) {
        close_geometry();
    }

    // FIXME: this is not a very nice way to implement this
    for(auto& detector : detectors_) {
        if(detector->getName() == name) {
            return detector;
        }
    }
    throw allpix::InvalidDetectorError(name);
}

std::vector<std::shared_ptr<Detector>> GeometryManager::getDetectorsByType(const std::string& type) {
    if(!closed_) {
        close_geometry();
    }

    std::vector<std::shared_ptr<Detector>> result;
    for(auto& detector : detectors_) {
        if(detector->getType() == type) {
            result.push_back(detector);
        }
    }
    if(result.empty()) {
        throw allpix::InvalidDetectorModelError(type);
    }

    return result;
}

void GeometryManager::load_models() {
    LOG(TRACE) << "Loading remaining default models";

    // Get paths to read models from
    std::vector<std::string> paths = getModelsPath();

    LOG(TRACE) << "Reading model files";
    // Add all the paths to the reader
    for(auto& path : paths) {
        // Check if file or directory
        if(std::filesystem::is_directory(path)) {
            for(const auto& entry : std::filesystem::directory_iterator(path)) {
                if(!entry.is_regular_file()) {
                    continue;
                }

                // Accept only with correct model suffix
                auto sub_path = std::filesystem::canonical(entry);
                std::string suffix(ALLPIX_MODEL_SUFFIX);
                if(sub_path.extension() != suffix) {
                    continue;
                }

                // Read model file and add model to list
                read_model_file(sub_path);
            }
        } else {
            // Always a file because paths are already checked
            read_model_file(path);
        }
    }
}

void GeometryManager::read_model_file(const std::filesystem::path& path) {
    auto model_name = path.stem();
    LOG(TRACE) << "Reading model " << model_name << " in path " << path;

    // Check if we need to look at file at all
    if(hasModel(model_name)) {
        LOG(DEBUG) << "Skipping overwritten model " << model_name << " in path " << path;
        return;
    }
    if(!needsModel(model_name)) {
        LOG(TRACE) << "Skipping not required model " << model_name << " in path " << path;
        return;
    }

    try {
        // Try to parse as config file
        std::ifstream file(path);
        const ConfigReader reader(file, path);

        // Parse configuration and add model to the config
        addModel(DetectorModel::factory(model_name, reader));

    } catch(const ConfigParseError& e) {
        // Not a valid config file, see https://gitlab.cern.ch/allpix-squared/allpix-squared/-/issues/277
        LOG(ERROR) << "Skipping invalid model file \"" << path << "\":" << std::endl << e.what();
    }
}

void GeometryManager::close_geometry() {
    LOG(TRACE) << "Starting geometry closing procedure";

    // Load all standard models
    load_models();

    // Try to resolve the missing models
    for(auto& [name, config_detectors] : nonresolved_models_) {
        for(auto& [config, detector] : config_detectors) {
            // Create a new model if one of the core model parameters is changed in the detector configuration
            auto model = getModel(name);

            // Get the configuration of the model
            Configuration new_config("");
            auto model_configs = model->getConfigurations();
            std::vector<std::string> valid_sections = {"", "implant", "support"};
            for(auto& conf : model_configs) {
                auto section_name = allpix::transform(conf.getName(), ::tolower);
                if(std::find(valid_sections.begin(), valid_sections.end(), section_name) == valid_sections.end()) {
                    LOG(WARNING) << "Section [" << section_name << "] is not valid in sensor geometry definition.";
                }
            }

            // Add all non internal parameters to the config for a specialized model
            for(auto& [key, value] : config.getAll()) {
                // Skip all internal parameters
                if(key == "type" || key == "position" || key == "orientation_mode" || key == "orientation" ||
                   key == "alignment_precision_position" || key == "alignment_precision_orientation" || key == "role") {
                    continue;
                }
                // Add the extra parameter to the new overwritten config
                new_config.setText(key, value);
            }

            // Create new model if needed
            if(new_config.countSettings() != 0) {
                ConfigReader reader;
                // Add the new configuration first to overwrite
                reader.addConfiguration(std::move(new_config));
                // Then add the original configuration
                for(auto& model_config : model_configs) {
                    reader.addConfiguration(std::move(model_config));
                }

                model = DetectorModel::factory(name, reader);
            }

            detector->set_model(std::move(model));
        }
    }

    closed_ = true;
    LOG(TRACE) << "Closed geometry";
}
std::pair<XYZPoint, Rotation3D> GeometryManager::calculate_orientation(const Configuration& config) {

    // Calculate possible detector misalignment to be added
    auto misalignment = [&](auto residuals) {
        double dx = allpix::normal_distribution<double>(0, residuals.x())(random_generator_);
        double dy = allpix::normal_distribution<double>(0, residuals.y())(random_generator_);
        double dz = allpix::normal_distribution<double>(0, residuals.z())(random_generator_);
        return DisplacementVector3D<Cartesian3D<double>>(dx, dy, dz);
    };

    // Get the position and apply potential misalignment
    auto position = config.get<XYZPoint>("position");
    LOG(DEBUG) << "Position:    " << Units::display(position, {"mm", "um"});
    position += misalignment(config.get<XYZPoint>("alignment_precision_position", XYZPoint()));
    LOG(DEBUG) << " misaligned: " << Units::display(position, {"mm", "um"});

    // Get the orientation and apply misalignment to the individual angles before combining them
    auto orient_vec = config.get<XYZVector>("orientation");
    LOG(DEBUG) << "Orientation: " << Units::display(orient_vec, {"deg"});
    orient_vec += misalignment(config.get<XYZVector>("alignment_precision_orientation", XYZVector()));
    LOG(DEBUG) << " misaligned: " << Units::display(orient_vec, {"deg"});

    auto orientation_mode = config.get<std::string>("orientation_mode", "xyz");
    Rotation3D orientation;
    if(orientation_mode == "zyx") {
        // First angle given in the configuration file is around z, second around y, last around x:
        LOG(DEBUG) << "Interpreting Euler angles as ZYX rotation";
        orientation = RotationZYX(orient_vec.x(), orient_vec.y(), orient_vec.z());
    } else if(orientation_mode == "xyz") {
        LOG(DEBUG) << "Interpreting Euler angles as XYZ rotation";
        // First angle given in the configuration file is around x, second around y, last around z:
        orientation = RotationZ(orient_vec.z()) * RotationY(orient_vec.y()) * RotationX(orient_vec.x());
    } else if(orientation_mode == "zxz") {
        LOG(DEBUG) << "Interpreting Euler angles as ZXZ rotation";
        // First angle given in the configuration file is around z, second around x, last around z:
        orientation = EulerAngles(orient_vec.x(), orient_vec.y(), orient_vec.z());
    } else {
        throw InvalidValueError(config, "orientation_mode", "orientation_mode should be either 'zyx', xyz' or 'zxz'");
    }
    return {position, orientation};
}

bool GeometryManager::hasMagneticField() const { return (magnetic_field_type_ != MagneticFieldType::NONE); }

void GeometryManager::setMagneticFieldFunction(MagneticFieldFunction function, MagneticFieldType type) {
    magnetic_field_function_ = std::move(function);
    magnetic_field_type_ = type;
}

MagneticFieldType GeometryManager::getMagneticFieldType() const { return magnetic_field_type_; }

ROOT::Math::XYZVector GeometryManager::getMagneticField(const ROOT::Math::XYZPoint& position) const {
    return magnetic_field_function_(position);
}

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