src/core/geometry/RadialStripDetectorModel.cpp

Implementation of radial strip detector model. More…

Detailed Description

Implementation of radial strip detector model.

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

#include <Math/RotationZ.h>
#include <Math/Transform3D.h>

using namespace allpix;

RadialStripDetectorModel::RadialStripDetectorModel(std::string type,
                                                   const std::shared_ptr<DetectorAssembly>& assembly,
                                                   const ConfigReader& reader,
                                                   const Configuration& config)
    : DetectorModel(std::move(type), assembly, reader, config) {

    if(std::dynamic_pointer_cast<MonolithicAssembly>(assembly) == nullptr) {
        throw InvalidCombinationError(config, {"type", "geometry"}, "this geometry only supports assembly type monolithic");
    }

    // Set geometry parameters from config file
    setNumberOfStrips(config.getArray<unsigned int>("number_of_strips"));
    setStripLength(config.getArray<double>("strip_length"));
    setAngularPitch(config.getArray<double>("angular_pitch"));
    setInnerPitch(config.getArray<double>("inner_pitch"));
    setStereoAngle(config.get<double>("stereo_angle", 0));

    // Get the number of strip rows
    auto strip_rows = static_cast<unsigned int>(number_of_strips_.size());

    // Check if parameters are defined for each strip row
    if(strip_length_.size() != strip_rows || angular_pitch_.size() != strip_rows || inner_pitch_.size() != strip_rows) {
        throw InvalidCombinationError(config,
                                      {"number_of_strips", "strip_length", "angular_pitch", "inner_pitch"},
                                      "The number of parameter values does not match the number of strip rows.");
    }

    // Dimension checks
    for(unsigned int row = 0; row < strip_rows; row++) {
        // Check if strip pitch is smaller than the length
        if(inner_pitch_.at(row) > strip_length_.at(row)) {
            throw InvalidValueError(
                config, "inner_pitch", "Inner pitch in row " + std::to_string(row) + " is larger than strip length.");
        }
        // Check that sensor segment doesn't subtend too large an angle,
        auto angle = angular_pitch_.at(row) * number_of_strips_.at(row);
        if(angle > TMath::Pi() / 2) {
            throw InvalidValueError(config, "angular_pitch", "Wafer cannot subtend a larger angle than pi/2.");
        }
        row_angle_.push_back(angle);
    }

    // Calculations of trapezoidal wrapper dimensions
    // Total strip length
    auto total_strip_length = std::accumulate(strip_length_.begin(), strip_length_.end(), 0);
    // Distance from wrapper inner edge to its focal point
    auto radius_extension = inner_pitch_.at(0) / (2 * tan(0.5 * angular_pitch_.at(0)));
    // Maximum angle subtended by the widest strip row
    auto max_angle = getRowAngleMax();
    // Distance from inner radius to wrapper inner edge
    auto strip_extension = radius_extension * (1 - cos(max_angle / 2));

    // Set the smaller base of the trapezoidal wrapper
    setSensorBaseInner(2 * radius_extension * sin(max_angle / 2));
    // Set the larger base of the trapezoidal wrapper
    setSensorBaseOuter(2 * (radius_extension + total_strip_length) * sin(max_angle / 2));
    // Set the total length of the trapezoidal wrapper
    setSensorLength(total_strip_length + strip_extension);

    // Set segment radii
    // First segment radius is the strip extension and radius extension combined
    row_radius_.push_back(strip_extension + radius_extension);
    for(unsigned int row = 0; row < strip_rows; row++) {
        // Subsequent segment radii calculated as previous segment radius plus strip length
        row_radius_.push_back(row_radius_.at(row) + strip_length_.at(row));
    }

    // Set number of strips; x-value is the maximum number of strips
    // in all rows, y-value is the number of rows
    setNPixels({*std::max_element(number_of_strips_.begin(), number_of_strips_.end()), strip_rows});
    // Pixel size is defined as the rectangular wrapper size divided by the maximum
    // number of strips (x-value) or strip rows (y-value)
    setPixelSize({getSize().x() / number_of_pixels_.x(), getSize().y() / number_of_pixels_.y()});

    // Translation vector from local coordinate center to sensor focal point
    focus_translation_ = {getCenterRadius() * sin(stereo_angle_), getCenterRadius() * (1 - cos(stereo_angle_)), 0};
}

bool RadialStripDetectorModel::isWithinSensor(const ROOT::Math::XYZPoint& local_pos) const {
    // Convert local position to polar coordinates
    auto polar_pos = getPositionPolar(local_pos);
    // Check if radial coordinate is outside the sensor
    if(2 * std::fabs(local_pos.z() - getSensorCenter().z()) > getSensorSize().z() ||
       (polar_pos.r() > row_radius_.back() || polar_pos.r() < row_radius_.front())) {
        return false;
    }
    // Find which strip row the position belongs to
    for(unsigned int row = 0; row < getNPixels().y(); row++) {
        if(polar_pos.r() > row_radius_.at(row) && polar_pos.r() <= row_radius_.at(row + 1)) {
            // Check if the angular coordinate is within that strip row
            return (std::fabs(polar_pos.phi() + stereo_angle_) <= angular_pitch_.at(row) * number_of_strips_.at(row) / 2);
        }
    }
    return false;
}

bool RadialStripDetectorModel::isOnSensorBoundary(const ROOT::Math::XYZPoint& local_pos) const {
    // Convert local position to polar coordinates
    auto polar_pos = getPositionPolar(local_pos);
    // Check if radial coordinate is on the sensor edge
    if(2 * std::fabs(local_pos.z()) == getSensorSize().z() ||
       (polar_pos.r() == row_radius_.back() || polar_pos.r() == row_radius_.front())) {
        return true;
    }
    // Find which strip row the position belongs to
    for(unsigned int row = 0; row < getNPixels().y(); row++) {
        if(polar_pos.r() > row_radius_.at(row) && polar_pos.r() <= row_radius_.at(row + 1)) {
            // Check if the angular coordinate is on the edge of strip row
            return (std::fabs(polar_pos.phi() + stereo_angle_) == angular_pitch_.at(row) * number_of_strips_.at(row) / 2);
        }
    }
    return false;
}

bool RadialStripDetectorModel::isWithinMatrix(const Pixel::Index& strip_index) const {
    return !(strip_index.y() < 0 || strip_index.y() >= static_cast<int>(getNPixels().y()) || strip_index.x() < 0 ||
             strip_index.x() >= static_cast<int>(number_of_strips_.at(static_cast<unsigned int>(strip_index.y()))));
}

ROOT::Math::Polar2DPoint RadialStripDetectorModel::getPositionPolar(const ROOT::Math::XYZPoint& local_pos) const {
    // Calculate the radial component
    auto r = sqrt(local_pos.x() * local_pos.x() + local_pos.y() * local_pos.y());
    // Shift the coordinate origin to the strip focal point
    auto focus_pos = local_pos - focus_translation_;
    // Calculate the angular component obtained from the corrected position
    auto phi = atan2(focus_pos.x(), focus_pos.y());

    return {r, phi};
}

ROOT::Math::XYPoint RadialStripDetectorModel::getPositionCartesian(const ROOT::Math::Polar2DPoint& polar_pos) const {
    // Length of the translation vector from the local center to the focal point
    auto len_foc = std::sqrt(focus_translation_.mag2());
    // Calculate two relevant angles needed for the transformation of the angular component to be measured from the local
    // coordinate center instead of the strip focal point
    auto alpha = std::acos(len_foc / (2 * getCenterRadius()));
    auto gamma = asin(len_foc * sin(alpha + polar_pos.phi() + stereo_angle_) / polar_pos.r());
    // Transform the angle
    auto phi = 2 * alpha + gamma + polar_pos.phi() + stereo_angle_ - ROOT::Math::Pi();

    return {polar_pos.r() * sin(phi), polar_pos.r() * cos(phi)};
}

ROOT::Math::XYZPoint RadialStripDetectorModel::getPixelCenter(int x, int y) const {
    // Calculate the radial coordinate of the strip center
    auto local_r = (row_radius_.at(static_cast<unsigned int>(y)) + row_radius_.at(static_cast<unsigned int>(y + 1))) / 2;
    // Calculate the angular coordinate of the strip center
    auto local_phi =
        -angular_pitch_.at(static_cast<unsigned int>(y)) * number_of_strips_.at(static_cast<unsigned int>(y)) / 2 +
        (x + 0.5) * angular_pitch_.at(static_cast<unsigned int>(y)) - stereo_angle_;

    // Convert strip center position to cartesian coordinates
    auto center = getPositionCartesian(ROOT::Math::Polar2DPoint(local_r, local_phi));
    auto local_x = center.x();
    auto local_y = center.y();
    auto local_z = getSensorCenter().z() - getSensorSize().z() / 2.0;

    return {local_x, local_y, local_z};
}

std::pair<int, int> RadialStripDetectorModel::getPixelIndex(const ROOT::Math::XYZPoint& position) const {
    // Convert local position to polar coordinates
    auto polar_pos = getPositionPolar(position);

    // Get row index
    int strip_y{};
    for(unsigned int row = 0; row < getNPixels().y(); row++) {
        // Find the correct strip row by comparing to inner and outer row radii
        if(polar_pos.r() > row_radius_.at(row) && polar_pos.r() <= row_radius_.at(row + 1)) {
            strip_y = static_cast<int>(row);
            break;
        }
    }
    // Get the strip pitch in the correct strip row
    auto pitch = angular_pitch_.at(static_cast<unsigned int>(strip_y));
    // Calculate the strip x-index
    auto strip_x = static_cast<int>(std::floor(
        (polar_pos.phi() + stereo_angle_ + pitch * number_of_strips_.at(static_cast<unsigned int>(strip_y)) / 2) / pitch));

    return {strip_x, strip_y};
}

std::set<Pixel::Index> RadialStripDetectorModel::getNeighbors(const Pixel::Index& idx, const size_t distance) const {
    // Vector to hold the neighbor indices
    std::vector<Pixel::Index> neighbors;

    // Position of the global seed in polar coordinates
    auto seed_pol = getPositionPolar(getPixelCenter(idx.x(), idx.y()));

    // Iterate over eligible strip rows
    for(int y = static_cast<int>(-distance); y <= static_cast<int>(distance); y++) {
        // Skip row if outside of pixel matrix
        if(!isWithinMatrix(0, idx.y() + y)) {
            continue;
        }

        // Set starting position of a row seed to the global seed position
        auto row_seed_r = seed_pol.r();

        // Move row seed position to the center of a requested row
        for(unsigned int shift_y = 1; shift_y <= std::labs(y); shift_y++) {
            // Add or subtract position based on whether given row is below or above global seed
            row_seed_r += (y < 0) ? -strip_length_.at(static_cast<unsigned int>(idx.y()) - shift_y + 1) / 2 -
                                        strip_length_.at(static_cast<unsigned int>(idx.y()) - shift_y) / 2
                                  : strip_length_.at(static_cast<unsigned int>(idx.y()) + shift_y - 1) / 2 +
                                        strip_length_.at(static_cast<unsigned int>(idx.y()) + shift_y) / 2;
        }

        // Get cartesian position and pixel indices of the row seed
        auto row_seed = getPositionCartesian({row_seed_r, seed_pol.phi()});
        auto [row_seed_x, row_seed_y] = getPixelIndex({row_seed.x(), row_seed.y(), 0});

        // Iterate over potential neighbors of the row seed
        for(int j = static_cast<int>(-distance); j <= static_cast<int>(distance); j++) {
            // Add to final neighbors if strip is within the pixel matrix
            if(isWithinMatrix(row_seed_x + j, row_seed_y)) {
                neighbors.emplace_back(row_seed_x + j, row_seed_y);
            }
        }
    }

    return {neighbors.begin(), neighbors.end()};
}

bool RadialStripDetectorModel::areNeighbors(const Pixel::Index& seed,
                                            const Pixel::Index& entrant,
                                            const size_t distance) const {
    // If either pixel is outside of matrix, return false
    if(!isWithinMatrix(seed) || !isWithinMatrix(entrant)) {
        return false;
    }

    // y-index distance between the seed and the entrant
    auto dist_y = entrant.y() - seed.y();

    // Seed and entrant in the same strip row
    if(dist_y == 0) {
        // Compare x-index distance to the requested distance
        return (static_cast<size_t>(std::abs(seed.x() - entrant.x())) <= distance);
    }

    // Position of the global seed in polar coordinates
    auto seed_pol = getPositionPolar(getPixelCenter(seed.x(), seed.y()));

    // Set starting position of a row seed to the global seed position
    auto row_seed_r = seed_pol.r();

    // Move row seed position to the center of the requested row
    for(unsigned int shift_y = 0; shift_y < static_cast<unsigned int>(std::abs(dist_y)); shift_y++) {
        row_seed_r += (dist_y < 0) ? -strip_length_.at(static_cast<unsigned int>(seed.y()) - shift_y) / 2 -
                                         strip_length_.at(static_cast<unsigned int>(seed.y()) - shift_y - 1) / 2
                                   : strip_length_.at(static_cast<unsigned int>(seed.y()) + shift_y) / 2 +
                                         strip_length_.at(static_cast<unsigned int>(seed.y()) + shift_y + 1) / 2;
    }

    // Get cartesian position and pixel indices of the row seed
    auto row_seed = getPositionCartesian({row_seed_r, seed_pol.phi()});
    auto [row_seed_x, row_seed_y] = getPixelIndex({row_seed.x(), row_seed.y(), 0});

    // Compare row seed and entrant positions
    return (static_cast<size_t>(std::abs(row_seed_x - entrant.x())) <= distance) &&
           (static_cast<size_t>(std::abs(dist_y)) <= distance);
}

ROOT::Math::XYZPoint RadialStripDetectorModel::getSensorIntercept(const ROOT::Math::XYZPoint& inside,
                                                                  const ROOT::Math::XYZPoint& outside) const {

    auto check_position = outside;
    check_position.SetZ(inside.z());
    if(std::fabs(outside.z()) > getSensorSize().z() / 2.0 && isWithinSensor(check_position)) {
        // Carrier left sensor on the top or bottom surface of the sensor, interpolate end point on surface
        auto z_cur_border = std::fabs(outside.z() - getSensorSize().z() / 2.0);
        auto z_last_border = std::fabs(getSensorSize().z() / 2.0 - inside.z());
        auto z_total = z_cur_border + z_last_border;
        return (z_last_border / z_total) * static_cast<ROOT::Math::XYZVector>(outside) + (z_cur_border / z_total) * inside;
    } else {
        // Carrier left sensor on any other border, use last position inside instead
        return inside;
    }
}

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