Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-27 07:23:58

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 // Project include(s)
0012 #include "detray/definitions/algebra.hpp"
0013 #include "detray/definitions/containers.hpp"
0014 #include "detray/definitions/detail/qualifiers.hpp"
0015 #include "detray/definitions/indexing.hpp"
0016 #include "detray/definitions/math.hpp"
0017 #include "detray/definitions/units.hpp"
0018 #include "detray/geometry/coordinates/cartesian3D.hpp"
0019 #include "detray/geometry/coordinates/polar2D.hpp"
0020 #include "detray/geometry/detail/shape_utils.hpp"
0021 #include "detray/geometry/detail/vertexer.hpp"
0022 
0023 // System include(s)
0024 #include <limits>
0025 #include <ostream>
0026 #include <string_view>
0027 
0028 namespace detray {
0029 
0030 /// @brief Geometrical shape of a stereo annulus that is used for the ITk
0031 /// strip endcaps.
0032 ///
0033 /// The stereo annulus is defined in two different(!) polar coordinate systems
0034 /// that differ by an origin shift. The boundaries are the inner and outer
0035 /// radius (bounds[0] and bounds[1]) in the polar coordinate system of an
0036 /// endcap disc (called beam system in the following) that is centered on
0037 /// the beam axis, as well as the two phi boundaries that are defined in the
0038 /// system that is shifted into the focal point of the strips on a given sensor
0039 /// (called focal system in the following). Note, that the local coordinate
0040 /// system of the annulus surface is the same as the shifted disc (focal)
0041 /// system! The mask phi boundaries (bounds[2] and bounds[3]) are defined
0042 /// relative to the average phi position ( bounds[6]) of the strips in the
0043 /// focal system.
0044 /// Due to the focal polar coordinate system of the strips needing a different
0045 /// origin from the beam polar system, two additional conversion parameters are
0046 /// included (bounds[4], bounds[5]). These are the origin shift in x and y
0047 /// respectively.
0048 class annulus2D {
0049  public:
0050   /// The name for this shape
0051   static constexpr std::string_view name = "(stereo) annulus2D";
0052 
0053   /// Names for the mask boundary values
0054   enum boundaries : unsigned int {
0055     e_min_r = 0u,
0056     e_max_r = 1u,
0057     e_min_phi_rel = 2u,
0058     e_max_phi_rel = 3u,
0059     e_average_phi = 4u,
0060     e_shift_x = 5u,
0061     e_shift_y = 6u,
0062     e_size = 7u,
0063   };
0064 
0065   /// Container definition for the shape boundary values
0066   template <concepts::scalar scalar_t>
0067   using bounds_type = darray<scalar_t, boundaries::e_size>;
0068 
0069   /// Local coordinate frame ( focal system )
0070   template <concepts::algebra algebra_t>
0071   using local_frame_type = polar2D<algebra_t>;
0072 
0073   /// Result type of a boundary check
0074   template <typename bool_t>
0075   using result_type = detail::boundary_check_result<bool_t>;
0076 
0077   /// Dimension of the local coordinate system
0078   static constexpr std::size_t dim{2u};
0079 
0080   /// @returns the stereo angle calculated from the mask @param bounds .
0081   template <concepts::scalar scalar_t>
0082   DETRAY_HOST_DEVICE darray<scalar_t, 8> stereo_angle(
0083       const bounds_type<scalar_t> &bounds) const {
0084     // Half stereo angle (phi_s / 2) (y points in the long strip direction)
0085     return 2.f * math::atan(bounds[e_shift_y] / bounds[e_shift_x]);
0086   }
0087 
0088   /// @returns The phi position in relative to the average phi of the annulus.
0089   template <concepts::scalar scalar_t, concepts::point point_t>
0090   DETRAY_HOST_DEVICE inline scalar_t get_phi_rel(
0091       const bounds_type<scalar_t> &bounds, const point_t &loc_p) const {
0092     // Rotate by avr phi in the focal system (this is usually zero)
0093     return loc_p[1] - bounds[e_average_phi];
0094   }
0095 
0096   /// @returns The squared radial position in the beam frame.
0097   template <concepts::scalar scalar_t, concepts::point point_t>
0098   DETRAY_HOST_DEVICE inline scalar_t get_r2_beam_frame(
0099       const bounds_type<scalar_t> &bounds, const point_t &loc_p) const {
0100     // Go to beam frame to check r boundaries. Use the origin
0101     // shift in polar coordinates for that
0102     // TODO: Put shift in r-phi into the bounds?
0103     point_t shift_xy{};
0104     shift_xy[0u] = -bounds[e_shift_x];
0105     shift_xy[1u] = -bounds[e_shift_y];
0106     const scalar_t shift_r = vector::perp(shift_xy);
0107     const scalar_t shift_phi = vector::phi(shift_xy);
0108 
0109     return shift_r * shift_r + loc_p[0] * loc_p[0] +
0110            2.f * shift_r * loc_p[0] *
0111                math::cos(get_phi_rel(bounds, loc_p) - shift_phi);
0112   }
0113 
0114   /// @brief Find the minimum distance to any boundary.
0115   ///
0116   /// @note the point is expected to be given in local coordinates by the
0117   /// caller. For the annulus shape, the local coordinate system of the
0118   /// strips is used (focal system).
0119   ///
0120   /// @param bounds the boundary values for this shape
0121   /// @param loc_p the point to be checked in the local coordinate system
0122   ///
0123   /// @return the minimum distance.
0124   template <concepts::scalar scalar_t, concepts::point point_t>
0125   DETRAY_HOST_DEVICE inline scalar_t min_dist_to_boundary(
0126       const bounds_type<scalar_t> &bounds, const point_t &loc_p) const {
0127     // The two quantities to check: r^2 in beam system, phi in focal system:
0128 
0129     // Rotate by avr phi in the focal system (this is usually zero)
0130     const scalar_t phi_rel_focal = get_phi_rel(bounds, loc_p);
0131 
0132     // Check phi boundaries, which are well def. in focal frame
0133     const scalar_t min_phi_dist =
0134         math::min(math::fabs(phi_rel_focal - bounds[e_min_phi_rel]),
0135                   math::fabs(bounds[e_max_phi_rel] - phi_rel_focal));
0136 
0137     const auto r_beam = math::sqrt(get_r2_beam_frame(bounds, loc_p));
0138 
0139     const scalar_t min_r_dist = math::min(math::fabs(r_beam - bounds[e_min_r]),
0140                                           math::fabs(bounds[e_max_r] - r_beam));
0141 
0142     // Compare the radius with the chord
0143     return math::min(min_r_dist,
0144                      2.f * loc_p[0] * math::sin(0.5f * min_phi_dist));
0145   }
0146 
0147   /// @brief Check boundary values for a local point.
0148   /// @{
0149   /// @param bounds the boundary values for this shape
0150   /// @param trf the surface transform
0151   /// @param glob_p the point to be checked in the global coordinate system
0152   /// @param tol dynamic tolerance determined by caller
0153   ///
0154   /// @return true if the local point lies within the given boundaries.
0155   template <concepts::algebra algebra_t>
0156   DETRAY_HOST_DEVICE constexpr result_type<dbool<algebra_t>> check_boundaries(
0157       const bounds_type<dscalar<algebra_t>> &bounds,
0158       const dtransform3D<algebra_t> &trf, const dpoint3D<algebra_t> &glob_p,
0159       const dscalar<algebra_t> tol =
0160           std::numeric_limits<dscalar<algebra_t>>::epsilon(),
0161       const dscalar<algebra_t> edge_tol = 0.f) const {
0162     using scalar_t = dscalar<algebra_t>;
0163 
0164     // Move point to local plane: Focal frame in cartesian coordinates
0165     const auto loc_3D{cartesian3D<algebra_t>::global_to_local(trf, glob_p, {})};
0166 
0167     // Shift local 3D position into beam frame to check the radius
0168     const scalar_t new_x{loc_3D[0] + bounds[e_shift_x]};
0169     const scalar_t new_y{loc_3D[1] + bounds[e_shift_y]};
0170 
0171     const scalar_t r_beam{math::sqrt(math::fma(new_x, new_x, new_y * new_y))};
0172 
0173     auto inside_mask = ((r_beam + tol) >= bounds[e_min_r]) &&
0174                        (r_beam <= (bounds[e_max_r] + tol));
0175 
0176     // Try to avoid the costly phi calculation
0177     auto phi_focal{detail::invalid_value<scalar_t>()};
0178     if (detail::any_of(inside_mask)) {
0179       // Get phi for phi-bounds check and rotate by average phi
0180       phi_focal = vector::phi(loc_3D) - bounds[e_average_phi];
0181       // Estimate angular tolerance along r
0182       const scalar_t phi_tol{detail::phi_tolerance(tol, r_beam)};
0183 
0184       inside_mask = (phi_focal >= (bounds[e_min_phi_rel] - phi_tol)) &&
0185                     (phi_focal <= (bounds[e_max_phi_rel] + phi_tol)) &&
0186                     inside_mask;
0187     }
0188 
0189     decltype(inside_mask) inside_edge{false};
0190     if (detail::any_of(edge_tol > 0.f)) {
0191       // Edge tolerance
0192       const scalar_t full_tol{tol + edge_tol};
0193 
0194       inside_edge = ((r_beam + full_tol) >= bounds[e_min_r]) &&
0195                     (r_beam <= (bounds[e_max_r] + full_tol));
0196 
0197       if (detail::any_of(inside_edge)) {
0198         // If phi had not been calculated before, do it now
0199         if (detail::is_invalid_value(phi_focal)) {
0200           phi_focal = vector::phi(loc_3D) - bounds[e_average_phi];
0201         }
0202 
0203         const scalar_t phi_tol_full{detail::phi_tolerance(full_tol, r_beam)};
0204 
0205         inside_edge = (phi_focal >= (bounds[e_min_phi_rel] - phi_tol_full)) &&
0206                       (phi_focal <= (bounds[e_max_phi_rel] + phi_tol_full)) &&
0207                       inside_edge;
0208       }
0209     }
0210 
0211     return result_type<decltype(inside_mask)>{inside_mask, inside_edge};
0212   }
0213 
0214   /// @note the point is expected to be given in local coordinates by the
0215   /// caller. For the annulus shape, the local coordinate system of the
0216   /// strips is used (focal system).
0217   ///
0218   /// @param bounds the boundary values for this shape
0219   /// @param loc_p the point to be checked in the local coordinate system
0220   /// @param tol dynamic tolerance determined by caller
0221   ///
0222   /// @return true if the local point lies within the given boundaries.
0223   template <concepts::scalar scalar_t, concepts::point point_t>
0224   DETRAY_HOST_DEVICE constexpr auto check_boundaries(
0225       const bounds_type<scalar_t> &bounds, const point_t &loc_p,
0226       const scalar_t tol = std::numeric_limits<scalar_t>::epsilon(),
0227       const scalar_t edge_tol = 0.f) const {
0228     // The two quantities to check: r^2 in beam system, phi in focal system:
0229 
0230     // Rotate by avr phi in the focal system (this is usually zero)
0231     const scalar_t phi_focal = get_phi_rel(bounds, loc_p);
0232 
0233     // Check phi boundaries, which are well def. in focal frame
0234     const scalar_t phi_tol = detail::phi_tolerance(tol, loc_p[0]);
0235     auto inside_mask = !((phi_focal < (bounds[e_min_phi_rel] - phi_tol)) ||
0236                          (phi_focal > (bounds[e_max_phi_rel] + phi_tol)));
0237 
0238     // Try to avoid the costly r_beam calculation
0239     auto r_beam2{detail::invalid_value<scalar_t>()};
0240     if (detail::any_of(inside_mask)) {
0241       r_beam2 = get_r2_beam_frame(bounds, loc_p);
0242 
0243       // Apply tolerances as squares: 0 <= a, 0 <= b: a^2 <= b^2 <=> a <=
0244       // b
0245       const scalar_t minR_tol =
0246           math::max(bounds[e_min_r] - tol, static_cast<scalar_t>(0.f));
0247       const scalar_t maxR_tol = bounds[e_max_r] + tol;
0248 
0249       assert(detail::all_of(minR_tol >= static_cast<scalar_t>(0.f)));
0250 
0251       inside_mask = (r_beam2 >= (minR_tol * minR_tol)) &&
0252                     (r_beam2 <= (maxR_tol * maxR_tol)) && inside_mask;
0253     }
0254 
0255     decltype(inside_mask) inside_edge{false};
0256     if (detail::any_of(edge_tol > 0.f)) {
0257       // Edge tolerance
0258       const scalar_t full_tol{tol + edge_tol};
0259       const scalar_t phi_tol_full = detail::phi_tolerance(full_tol, loc_p[0]);
0260 
0261       const auto phi_check_edge =
0262           (phi_focal >= (bounds[e_min_phi_rel] - phi_tol_full)) &&
0263           (phi_focal <= (bounds[e_max_phi_rel] + phi_tol_full));
0264 
0265       if (detail::any_of(inside_edge)) {
0266         // If phi had not been calculated before, do it now
0267         if (detail::is_invalid_value(r_beam2)) {
0268           r_beam2 = get_r2_beam_frame(bounds, loc_p);
0269         }
0270 
0271         const scalar_t minR_tol_edge =
0272             math::max(bounds[e_min_r] - full_tol, static_cast<scalar_t>(0.f));
0273         const scalar_t maxR_tol_edge = bounds[e_max_r] + full_tol;
0274 
0275         assert(detail::all_of(minR_tol_edge >= static_cast<scalar_t>(0.f)));
0276 
0277         inside_edge = (r_beam2 >= (minR_tol_edge * minR_tol_edge)) &&
0278                       (r_beam2 <= (maxR_tol_edge * maxR_tol_edge)) &&
0279                       phi_check_edge;
0280       }
0281     }
0282 
0283     return result_type<decltype(inside_mask)>{inside_mask, inside_edge};
0284   }
0285   /// @}
0286 
0287   /// @brief Measure of the shape: Area
0288   ///
0289   /// @note (not yet implemented!)
0290   ///
0291   /// @param bounds the boundary values for this shape
0292   ///
0293   /// @returns the stereo annulus area on the plane.
0294   template <concepts::scalar scalar_t>
0295   DETRAY_HOST_DEVICE constexpr scalar_t measure(
0296       const bounds_type<scalar_t> &bounds) const {
0297     return area(bounds);
0298   }
0299 
0300   /// @brief The area of a the shape
0301   ///
0302   /// @note (not yet implemented!)
0303   ///
0304   /// @param bounds the boundary values for this shape
0305   ///
0306   /// @returns the stereo annulus area.
0307   template <concepts::scalar scalar_t>
0308   DETRAY_HOST_DEVICE constexpr scalar_t area(
0309       const bounds_type<scalar_t> & /*bounds*/) const {
0310     return detail::invalid_value<scalar_t>();
0311   }
0312 
0313   /// @brief Merge two annulus shapes
0314   ///
0315   /// @param bounds the boundary values for this shape
0316   /// @param o_bounds the boundary values for the other shape
0317   ///
0318   /// @returns merged bound values
0319   template <concepts::scalar scalar_t>
0320   DETRAY_HOST_DEVICE constexpr bounds_type<scalar_t> merge(
0321       const bounds_type<scalar_t> &bounds,
0322       const bounds_type<scalar_t> &o_bounds) const {
0323     assert(bounds[e_average_phi] == o_bounds[e_average_phi]);
0324     assert(bounds[e_shift_x] == o_bounds[e_shift_x]);
0325     assert(bounds[e_shift_y] == o_bounds[e_shift_y]);
0326 
0327     bounds_type<scalar_t> new_bounds{};
0328 
0329     new_bounds[e_min_r] = math::min(bounds[e_min_r], o_bounds[e_min_r]);
0330     new_bounds[e_max_r] = math::max(bounds[e_max_r], o_bounds[e_max_r]);
0331     new_bounds[e_min_phi_rel] =
0332         math::min(bounds[e_min_phi_rel], o_bounds[e_min_phi_rel]);
0333     new_bounds[e_max_phi_rel] =
0334         math::max(bounds[e_max_phi_rel], o_bounds[e_max_phi_rel]);
0335     new_bounds[e_average_phi] = bounds[e_average_phi];
0336     new_bounds[e_shift_x] = bounds[e_shift_x];
0337     new_bounds[e_shift_y] = bounds[e_shift_y];
0338 
0339     return new_bounds;
0340   }
0341 
0342   /// @brief Lower and upper point for minimal axis aligned bounding box.
0343   ///
0344   /// Computes the min and max vertices in a local cartesian frame.
0345   ///
0346   /// @param bounds the boundary values for this shape
0347   /// @param env dynamic envelope around the shape
0348   ///
0349   /// @returns and array of coordinates that contains the lower point (first
0350   /// three values) and the upper point (latter three values).
0351   // @TODO: this is a terrible approximation: restrict to annulus corners
0352   template <concepts::algebra algebra_t>
0353   DETRAY_HOST_DEVICE darray<dscalar<algebra_t>, 6> local_min_bounds(
0354       const bounds_type<dscalar<algebra_t>> &bounds,
0355       const dscalar<algebra_t> env =
0356           std::numeric_limits<dscalar<algebra_t>>::epsilon()) const {
0357     using scalar_t = dscalar<algebra_t>;
0358     using point_t = dpoint2D<algebra_t>;
0359 
0360     assert(env > 0.f);
0361 
0362     const auto c_pos = corners(bounds);
0363 
0364     const scalar_t o_x{bounds[e_shift_x]};
0365     const scalar_t o_y{bounds[e_shift_y]};
0366 
0367     // Corner points 'b' and 'c' in local cartesian beam system
0368     const point_t b{c_pos[4] * math::cos(c_pos[5]) - o_x,
0369                     c_pos[4] * math::sin(c_pos[5]) - o_y};
0370     const point_t c{c_pos[6] * math::cos(c_pos[7]) - o_x,
0371                     c_pos[6] * math::sin(c_pos[7]) - o_y};
0372 
0373     // bisector = 0.5 * (c + b). Scale to the length of the full circle to
0374     // get the outermost point
0375     const point_t t = bounds[e_max_r] * vector::normalize(c + b);
0376 
0377     // Find the min/max positions in x and y
0378     darray<scalar_t, 5> x_pos{c_pos[2] * math::cos(c_pos[3]) - o_x, b[0], c[0],
0379                               c_pos[0] * math::cos(c_pos[1]) - o_x, t[0]};
0380     darray<scalar_t, 5> y_pos{c_pos[2] * math::sin(c_pos[3]) - o_y, b[1], c[1],
0381                               c_pos[0] * math::sin(c_pos[1]) - o_y, t[1]};
0382 
0383     constexpr scalar_t inv{detail::invalid_value<scalar_t>()};
0384     scalar_t min_x{inv};
0385     scalar_t min_y{inv};
0386     scalar_t max_x{-inv};
0387     scalar_t max_y{-inv};
0388     for (unsigned int i{0u}; i < 5u; ++i) {
0389       min_x = x_pos[i] < min_x ? x_pos[i] : min_x;
0390       max_x = x_pos[i] > max_x ? x_pos[i] : max_x;
0391       min_y = y_pos[i] < min_y ? y_pos[i] : min_y;
0392       max_y = y_pos[i] > max_y ? y_pos[i] : max_y;
0393     }
0394 
0395     return {min_x - env, min_y - env, -env, max_x + env, max_y + env, env};
0396   }
0397 
0398   /// @brief Stereo annulus corners in polar strip system.
0399   ///
0400   /// @param bounds the boundary values for the stereo annulus
0401   ///
0402   /// @note see calculation of strip lengths in
0403   ///       https://cds.cern.ch/record/1514636?ln=en p10-13
0404   ///
0405   /// @returns an array of coordinates that contains the lower point (first
0406   /// four values) and the upper point (latter four values).
0407   template <concepts::scalar scalar_t>
0408   DETRAY_HOST_DEVICE darray<scalar_t, 8> corners(
0409       const bounds_type<scalar_t> &bounds) const {
0410     // Calculate the r-coordinate of a point in the strip system from the
0411     // circle arc radius (e.g. min_r) and the phi position in the strip
0412     // system (e.g. for the corners these are average_phi + min_phi_rel and
0413     // average_phi + max_phi_rel).
0414     auto get_strips_pc_r = [&bounds](const scalar_t R,
0415                                      const scalar_t phi) -> scalar_t {
0416       // f: Shift distance between beamline and focal system origin
0417       const scalar_t f2{bounds[e_shift_x] * bounds[e_shift_x] +
0418                         bounds[e_shift_y] * bounds[e_shift_y]};
0419 
0420       // f * sin(phi_s / 2 + phi) using: f_y / f = sin(phi_s / 2) and
0421       // sin(a + b) = sin(a)*cos(b) + cos(a)*sin(b)
0422       const scalar_t f_sin_phi{bounds[e_shift_x] * math::cos(phi) +
0423                                bounds[e_shift_y] * math::sin(phi)};
0424 
0425       return f_sin_phi + math::sqrt(f_sin_phi * f_sin_phi - f2 + R * R);
0426     };
0427 
0428     // Calculate the polar coordinates for the corners
0429     const scalar_t min_phi{bounds[e_average_phi] + bounds[e_min_phi_rel]};
0430     const scalar_t max_phi{bounds[e_average_phi] + bounds[e_max_phi_rel]};
0431     darray<scalar_t, 8> corner_pos;
0432     // bottom left: min_r, min_phi_rel
0433     corner_pos[0] = get_strips_pc_r(bounds[e_min_r], min_phi);
0434     corner_pos[1] = min_phi;
0435     // bottom right: min_r, max_phi_rel
0436     corner_pos[2] = get_strips_pc_r(bounds[e_min_r], max_phi);
0437     corner_pos[3] = max_phi;
0438     // top right: max_r, max_phi_rel
0439     corner_pos[4] = get_strips_pc_r(bounds[e_max_r], max_phi);
0440     corner_pos[5] = max_phi;
0441     // top left: max_r, min_phi_rel
0442     corner_pos[6] = get_strips_pc_r(bounds[e_max_r], min_phi);
0443     corner_pos[7] = min_phi;
0444 
0445     return corner_pos;
0446   }
0447 
0448   /// @returns the shapes centroid in local cartesian coordinates
0449   /// @note the calculated centroid position is only an approximation
0450   /// (centroid of the four corner points)!
0451   template <concepts::algebra algebra_t>
0452   DETRAY_HOST_DEVICE dpoint3D<algebra_t> centroid(
0453       const bounds_type<dscalar<algebra_t>> &bounds) const {
0454     using scalar_t = dscalar<algebra_t>;
0455 
0456     // Strip polar system
0457     const auto crns = corners(bounds);
0458 
0459     // Coordinates of the centroid position in strip system
0460     const scalar_t r{0.25f * (crns[0] + crns[2] + crns[4] + crns[6])};
0461     const scalar_t phi{bounds[e_average_phi]};
0462 
0463     return r * dpoint3D<algebra_t>{math::cos(phi), math::sin(phi),
0464                                    static_cast<scalar_t>(0)};
0465   }
0466 
0467   /// Generate vertices in local cartesian frame
0468   ///
0469   /// @param bounds the boundary values for the stereo annulus
0470   /// @param n_seg is the number of line segments
0471   ///
0472   /// @return a generated list of vertices
0473   template <concepts::algebra algebra_t>
0474   DETRAY_HOST dvector<dpoint3D<algebra_t>> vertices(
0475       const bounds_type<dscalar<algebra_t>> &bounds, dindex n_seg) const {
0476     using scalar_t = dscalar<algebra_t>;
0477     using point2_t = dpoint2D<algebra_t>;
0478     using point3_t = dpoint3D<algebra_t>;
0479 
0480     scalar_t min_r = bounds[e_min_r];
0481     scalar_t max_r = bounds[e_max_r];
0482     scalar_t min_phi = bounds[e_average_phi] + bounds[e_min_phi_rel];
0483     scalar_t max_phi = bounds[e_average_phi] + bounds[e_max_phi_rel];
0484     scalar_t origin_x = bounds[e_shift_x];
0485     scalar_t origin_y = bounds[e_shift_y];
0486 
0487     point2_t origin_m = {origin_x, origin_y};
0488 
0489     /// Helper method: find inner outer radius at edges in STRIP PC
0490     auto circIx = [](scalar_t O_x, scalar_t O_y, scalar_t r,
0491                      scalar_t phi) -> point2_t {
0492       //                      ____________________________________________
0493       //                     /      2  2                    2    2  2    2
0494       //     O_x + O_y*m - \/  - O_x *m  + 2*O_x*O_y*m - O_y  + m *r  + r
0495       // x =
0496       // --------------------------------------------------------------
0497       //                                  2
0498       //                                 m  + 1
0499       //
0500       // y = m*x
0501       //
0502       scalar_t m = math::tan(phi);
0503       point2_t dir = {math::cos(phi), math::sin(phi)};
0504       scalar_t x1 = (O_x + O_y * m -
0505                      math::sqrt(-math::pow(O_x, 2.f) * math::pow(m, 2.f) +
0506                                 2.f * O_x * O_y * m - math::pow(O_y, 2.f) +
0507                                 math::pow(m, 2.f) * math::pow(r, 2.f) +
0508                                 math::pow(r, 2.f))) /
0509                     (math::pow(m, 2.f) + 1.f);
0510       scalar_t x2 = (O_x + O_y * m +
0511                      math::sqrt(-math::pow(O_x, 2.f) * math::pow(m, 2.f) +
0512                                 2.f * O_x * O_y * m - math::pow(O_y, 2.f) +
0513                                 math::pow(m, 2.f) * math::pow(r, 2.f) +
0514                                 math::pow(r, 2.f))) /
0515                     (math::pow(m, 2.f) + 1.f);
0516 
0517       if (point2_t v1 = {x1, m * x1}; vector::dot(v1, dir) > 0.f) {
0518         return v1;
0519       }
0520       return {x2, m * x2};
0521     };
0522 
0523     // calculate corners in STRIP XY
0524     point2_t ul_xy = circIx(origin_x, origin_y, max_r, max_phi);
0525     point2_t ll_xy = circIx(origin_x, origin_y, min_r, max_phi);
0526     point2_t ur_xy = circIx(origin_x, origin_y, max_r, min_phi);
0527     point2_t lr_xy = circIx(origin_x, origin_y, min_r, min_phi);
0528 
0529     auto inner_phi = detail::phi_values(vector::phi(lr_xy - origin_m),
0530                                         vector::phi(ll_xy - origin_m), n_seg);
0531     auto outer_phi = detail::phi_values(vector::phi(ul_xy - origin_m),
0532                                         vector::phi(ur_xy - origin_m), n_seg);
0533 
0534     dvector<point3_t> annulus_vertices;
0535     annulus_vertices.reserve(inner_phi.size() + outer_phi.size());
0536     for (scalar_t iphi : inner_phi) {
0537       annulus_vertices.push_back(point3_t{min_r * math::cos(iphi) + origin_x,
0538                                           min_r * math::sin(iphi) + origin_y,
0539                                           0.f});
0540     }
0541 
0542     for (scalar_t ophi : outer_phi) {
0543       annulus_vertices.push_back(point3_t{max_r * math::cos(ophi) + origin_x,
0544                                           max_r * math::sin(ophi) + origin_y,
0545                                           0.f});
0546     }
0547 
0548     return annulus_vertices;
0549   }
0550 
0551   /// @brief Check consistency of boundary values.
0552   ///
0553   /// @param bounds the boundary values for this shape
0554   /// @param os output stream for error messages
0555   ///
0556   /// @return true if the bounds are consistent.
0557   template <concepts::scalar scalar_t>
0558   DETRAY_HOST constexpr bool check_consistency(
0559       const bounds_type<scalar_t> &bounds, std::ostream &os) const {
0560     constexpr auto tol{10.f * std::numeric_limits<scalar_t>::epsilon()};
0561 
0562     if (math::signbit(bounds[e_min_r]) || bounds[e_max_r] < tol) {
0563       os << "DETRAY ERROR (HOST): Radial bounds must be in the range [0, "
0564             "numeric_max)";
0565       return false;
0566     }
0567     if (bounds[e_min_r] >= bounds[e_max_r] ||
0568         math::fabs(bounds[e_min_r] - bounds[e_max_r]) < tol) {
0569       os << "DETRAY ERROR (HOST): Min radius must be smaller than max "
0570             "radius.";
0571       return false;
0572     }
0573     if ((bounds[e_min_phi_rel] < -constant<scalar_t>::pi ||
0574          bounds[e_min_phi_rel] > constant<scalar_t>::pi) ||
0575         (bounds[e_max_phi_rel] < -constant<scalar_t>::pi ||
0576          bounds[e_max_phi_rel] > constant<scalar_t>::pi) ||
0577         (bounds[e_average_phi] < -constant<scalar_t>::pi ||
0578          bounds[e_average_phi] > constant<scalar_t>::pi)) {
0579       os << "DETRAY ERROR (HOST): Angles must map onto [-pi, pi] range.";
0580       return false;
0581     }
0582     if (bounds[e_min_phi_rel] >= bounds[e_max_phi_rel] ||
0583         math::fabs(bounds[e_min_phi_rel] - bounds[e_max_phi_rel]) < tol) {
0584       os << "DETRAY ERROR (HOST): Min relative angle must be smaller "
0585             "than max relative "
0586             "angle.";
0587       return false;
0588     }
0589 
0590     return true;
0591   }
0592 };
0593 
0594 }  // namespace detray