Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-05 08:10:22

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2020-2023 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 http://mozilla.org/MPL/2.0/.
0008 
0009 #include "Acts/Plugins/TGeo/TGeoSurfaceConverter.hpp"
0010 
0011 #include "Acts/Definitions/Tolerance.hpp"
0012 #include "Acts/Plugins/TGeo/TGeoPrimitivesHelper.hpp"
0013 #include "Acts/Surfaces/AnnulusBounds.hpp"
0014 #include "Acts/Surfaces/ConvexPolygonBounds.hpp"
0015 #include "Acts/Surfaces/CylinderBounds.hpp"
0016 #include "Acts/Surfaces/CylinderSurface.hpp"
0017 #include "Acts/Surfaces/DiscSurface.hpp"
0018 #include "Acts/Surfaces/PlaneSurface.hpp"
0019 #include "Acts/Surfaces/RadialBounds.hpp"
0020 #include "Acts/Surfaces/RectangleBounds.hpp"
0021 #include "Acts/Surfaces/Surface.hpp"
0022 #include "Acts/Surfaces/TrapezoidBounds.hpp"
0023 #include "Acts/Utilities/Helpers.hpp"
0024 
0025 #include <algorithm>
0026 #include <array>
0027 #include <cctype>
0028 #include <cstddef>
0029 #include <memory>
0030 #include <stdexcept>
0031 #include <tuple>
0032 #include <utility>
0033 #include <vector>
0034 
0035 #include <boost/algorithm/string.hpp>
0036 #include <boost/algorithm/string/predicate.hpp>
0037 
0038 #include "RtypesCore.h"
0039 #include "TGeoArb8.h"
0040 #include "TGeoBBox.h"
0041 #include "TGeoBoolNode.h"
0042 #include "TGeoCompositeShape.h"
0043 #include "TGeoMatrix.h"
0044 #include "TGeoShape.h"
0045 #include "TGeoTrd1.h"
0046 #include "TGeoTrd2.h"
0047 #include "TGeoTube.h"
0048 
0049 std::tuple<std::shared_ptr<const Acts::CylinderBounds>, const Acts::Transform3,
0050            double>
0051 Acts::TGeoSurfaceConverter::cylinderComponents(const TGeoShape& tgShape,
0052                                                const Double_t* rotation,
0053                                                const Double_t* translation,
0054                                                const std::string& axes,
0055                                                double scalor) noexcept(false) {
0056   std::shared_ptr<const CylinderBounds> bounds = nullptr;
0057   Transform3 transform = Transform3::Identity();
0058   double thickness = 0.;
0059 
0060   // Check if it's a tube (segment)
0061   auto tube = dynamic_cast<const TGeoTube*>(&tgShape);
0062   if (tube != nullptr) {
0063     if (!boost::istarts_with(axes, "XY") && !boost::istarts_with(axes, "YX")) {
0064       throw std::invalid_argument(
0065           "TGeoShape -> CylinderSurface (full): can only be converted with "
0066           "'(x/X)(y/Y)(*)' or '(y/Y)(x/X)(*) axes.");
0067     }
0068 
0069     // The sign of the axes
0070     int xs = std::islower(axes.at(0)) != 0 ? -1 : 1;
0071     int ys = std::islower(axes.at(1)) != 0 ? -1 : 1;
0072 
0073     // Create translation and rotation
0074     Vector3 t(scalor * translation[0], scalor * translation[1],
0075               scalor * translation[2]);
0076     bool flipxy = !boost::istarts_with(axes, "X");
0077     Vector3 ax = flipxy ? xs * Vector3(rotation[1], rotation[4], rotation[7])
0078                         : xs * Vector3(rotation[0], rotation[3], rotation[6]);
0079     Vector3 ay = flipxy ? ys * Vector3(rotation[0], rotation[3], rotation[6])
0080                         : ys * Vector3(rotation[1], rotation[4], rotation[7]);
0081     Vector3 az = ax.cross(ay);
0082 
0083     double minR = tube->GetRmin() * scalor;
0084     double maxR = tube->GetRmax() * scalor;
0085     double deltaR = maxR - minR;
0086     double medR = 0.5 * (minR + maxR);
0087     double halfZ = tube->GetDz() * scalor;
0088     if (halfZ > deltaR) {
0089       transform = TGeoPrimitivesHelper::makeTransform(ax, ay, az, t);
0090       double halfPhi = M_PI;
0091       double avgPhi = 0.;
0092       // Check if it's a segment
0093       auto tubeSeg = dynamic_cast<const TGeoTubeSeg*>(tube);
0094       if (tubeSeg != nullptr) {
0095         double phi1 = toRadian(tubeSeg->GetPhi1());
0096         double phi2 = toRadian(tubeSeg->GetPhi2());
0097         if (std::abs(phi2 - phi1) < M_PI * (1. - s_epsilon)) {
0098           if (!boost::starts_with(axes, "X")) {
0099             throw std::invalid_argument(
0100                 "TGeoShape -> CylinderSurface (sectorial): can only be "
0101                 "converted "
0102                 "with "
0103                 "'(X)(y/Y)(*)' axes.");
0104           }
0105           halfPhi = 0.5 * (std::max(phi1, phi2) - std::min(phi1, phi2));
0106           avgPhi = 0.5 * (phi1 + phi2);
0107         }
0108       }
0109       bounds = std::make_shared<CylinderBounds>(medR, halfZ, halfPhi, avgPhi);
0110       thickness = deltaR;
0111     }
0112   }
0113   return {bounds, transform, thickness};
0114 }
0115 
0116 std::tuple<std::shared_ptr<const Acts::DiscBounds>, const Acts::Transform3,
0117            double>
0118 Acts::TGeoSurfaceConverter::discComponents(const TGeoShape& tgShape,
0119                                            const Double_t* rotation,
0120                                            const Double_t* translation,
0121                                            const std::string& axes,
0122                                            double scalor) noexcept(false) {
0123   using Line2D = Eigen::Hyperplane<double, 2>;
0124   std::shared_ptr<const DiscBounds> bounds = nullptr;
0125   Transform3 transform = Transform3::Identity();
0126 
0127   double thickness = 0.;
0128   // Special test for composite shape of silicon
0129   auto compShape = dynamic_cast<const TGeoCompositeShape*>(&tgShape);
0130   if (compShape != nullptr) {
0131     if (!boost::istarts_with(axes, "XY")) {
0132       throw std::invalid_argument(
0133           "TGeoShape -> DiscSurface (Annulus): can only be converted with "
0134           "'(x/X)(y/Y)(*)' "
0135           "axes");
0136     }
0137 
0138     // Create translation and rotation
0139     Vector3 t(scalor * translation[0], scalor * translation[1],
0140               scalor * translation[2]);
0141     Vector3 ax(rotation[0], rotation[3], rotation[6]);
0142     Vector3 ay(rotation[1], rotation[4], rotation[7]);
0143     Vector3 az(rotation[2], rotation[5], rotation[8]);
0144 
0145     transform = TGeoPrimitivesHelper::makeTransform(ax, ay, az, t);
0146 
0147     auto interNode = dynamic_cast<TGeoIntersection*>(compShape->GetBoolNode());
0148     if (interNode != nullptr) {
0149       auto baseTube = dynamic_cast<TGeoTubeSeg*>(interNode->GetLeftShape());
0150       if (baseTube != nullptr) {
0151         double rMin = baseTube->GetRmin() * scalor;
0152         double rMax = baseTube->GetRmax() * scalor;
0153         auto maskShape = dynamic_cast<TGeoArb8*>(interNode->GetRightShape());
0154         if (maskShape != nullptr) {
0155           auto maskTransform = interNode->GetRightMatrix();
0156           // Get the only vertices
0157           const Double_t* polyVrt = maskShape->GetVertices();
0158           // Apply the whole transformation stored for the
0159           // polyhedron, since there is a translation and
0160           // also a side flip that needs to be applied.
0161           // @TODO check that 3rd coordinate is not altered by
0162           // the transformation ?
0163           std::vector<Vector2> vertices;
0164           for (unsigned int v = 0; v < 8; v += 2) {
0165             std::array<double, 3> local{polyVrt[v + 0], polyVrt[v + 1], 0.};
0166             std::array<double, 3> global{};
0167             maskTransform->LocalToMaster(local.data(), global.data());
0168             Vector2 vtx = Vector2(global[0] * scalor, global[1] * scalor);
0169             vertices.push_back(vtx);
0170           }
0171 
0172           std::vector<std::pair<Vector2, Vector2>> boundLines;
0173           for (std::size_t i = 0; i < vertices.size(); ++i) {
0174             Vector2 a = vertices.at(i);
0175             Vector2 b = vertices.at((i + 1) % vertices.size());
0176             Vector2 ab = b - a;
0177             double phi = VectorHelpers::phi(ab);
0178 
0179             if (std::abs(phi) > 3 * M_PI / 4. || std::abs(phi) < M_PI / 4.) {
0180               if (a.norm() < b.norm()) {
0181                 boundLines.push_back(std::make_pair(a, b));
0182               } else {
0183                 boundLines.push_back(std::make_pair(b, a));
0184               }
0185             }
0186           }
0187 
0188           if (boundLines.size() != 2) {
0189             throw std::logic_error(
0190                 "Input DiscPoly bounds type does not have sensible edges.");
0191           }
0192 
0193           Line2D lA =
0194               Line2D::Through(boundLines[0].first, boundLines[0].second);
0195           Line2D lB =
0196               Line2D::Through(boundLines[1].first, boundLines[1].second);
0197           Vector2 ix = lA.intersection(lB);
0198 
0199           const Eigen::Translation3d originTranslation(ix.x(), ix.y(), 0.);
0200           const Vector2 originShift = -ix;
0201 
0202           // Update transform by prepending the origin shift translation
0203           transform = transform * originTranslation;
0204           // Transform phi line point to new origin and get phi
0205           double phi1 =
0206               VectorHelpers::phi(boundLines[0].second - boundLines[0].first);
0207           double phi2 =
0208               VectorHelpers::phi(boundLines[1].second - boundLines[1].first);
0209           double phiMax = std::max(phi1, phi2);
0210           double phiMin = std::min(phi1, phi2);
0211           double phiShift = 0.;
0212 
0213           // Create the bounds
0214           auto annulusBounds = std::make_shared<const AnnulusBounds>(
0215               rMin, rMax, phiMin, phiMax, originShift, phiShift);
0216 
0217           thickness = maskShape->GetDZ() * scalor;
0218           bounds = annulusBounds;
0219         }
0220       }
0221     }
0222   } else {
0223     // Check if it's a tube
0224     auto tube = dynamic_cast<const TGeoTube*>(&tgShape);
0225     if (tube != nullptr) {
0226       if (!boost::istarts_with(axes, "XY") &&
0227           !boost::istarts_with(axes, "YX")) {
0228         throw std::invalid_argument(
0229             "TGeoShape -> DiscSurface: can only be converted with "
0230             "'(x/X)(y/Y)(*)' or '(y/Y)(x/X)(*) axes.");
0231       }
0232 
0233       // The sign of the axes
0234       int xs = std::islower(axes.at(0)) != 0 ? -1 : 1;
0235       int ys = std::islower(axes.at(1)) != 0 ? -1 : 1;
0236 
0237       // Create translation and rotation
0238       Vector3 t(scalor * translation[0], scalor * translation[1],
0239                 scalor * translation[2]);
0240       Vector3 ax = xs * Vector3(rotation[0], rotation[3], rotation[6]);
0241       Vector3 ay = ys * Vector3(rotation[1], rotation[4], rotation[7]);
0242       Vector3 az = ax.cross(ay);
0243       transform = TGeoPrimitivesHelper::makeTransform(ax, ay, az, t);
0244 
0245       double minR = tube->GetRmin() * scalor;
0246       double maxR = tube->GetRmax() * scalor;
0247       double halfZ = tube->GetDz() * scalor;
0248       double halfPhi = M_PI;
0249       double avgPhi = 0.;
0250       // Check if it's a segment
0251       auto tubeSeg = dynamic_cast<const TGeoTubeSeg*>(tube);
0252       if (tubeSeg != nullptr) {
0253         double phi1 = toRadian(tubeSeg->GetPhi1());
0254         double phi2 = toRadian(tubeSeg->GetPhi2());
0255         if (std::abs(phi2 - phi1) < 2 * M_PI * (1. - s_epsilon)) {
0256           if (!boost::starts_with(axes, "X")) {
0257             throw std::invalid_argument(
0258                 "TGeoShape -> CylinderSurface (sectorial): can only be "
0259                 "converted "
0260                 "with "
0261                 "'(X)(y/Y)(*)' axes.");
0262           }
0263           halfPhi = 0.5 * (std::max(phi1, phi2) - std::min(phi1, phi2));
0264           avgPhi = 0.5 * (phi1 + phi2);
0265         }
0266       }
0267       bounds = std::make_shared<RadialBounds>(minR, maxR, halfPhi, avgPhi);
0268       thickness = 2 * halfZ;
0269     }
0270   }
0271   return {bounds, transform, thickness};
0272 }
0273 
0274 std::tuple<std::shared_ptr<const Acts::PlanarBounds>, const Acts::Transform3,
0275            double>
0276 Acts::TGeoSurfaceConverter::planeComponents(const TGeoShape& tgShape,
0277                                             const Double_t* rotation,
0278                                             const Double_t* translation,
0279                                             const std::string& axes,
0280                                             double scalor) noexcept(false) {
0281   // Create translation and rotation
0282   Vector3 t(scalor * translation[0], scalor * translation[1],
0283             scalor * translation[2]);
0284   Vector3 ax(rotation[0], rotation[3], rotation[6]);
0285   Vector3 ay(rotation[1], rotation[4], rotation[7]);
0286   Vector3 az(rotation[2], rotation[5], rotation[8]);
0287 
0288   std::shared_ptr<const PlanarBounds> bounds = nullptr;
0289 
0290   // Check if it's a box - always true, hence last ressort
0291   const TGeoBBox* box = dynamic_cast<const TGeoBBox*>(&tgShape);
0292 
0293   // Check if it's a trapezoid2
0294   const TGeoTrd1* trapezoid1 = dynamic_cast<const TGeoTrd1*>(&tgShape);
0295   if ((trapezoid1 != nullptr) && !boost::istarts_with(axes, "XZ")) {
0296     throw std::invalid_argument(
0297         "TGeoTrd1 -> PlaneSurface: can only be converted with '(x/X)(z/Z)(*)' "
0298         "axes");
0299   }
0300 
0301   // Check if it's a trapezoid2
0302   const TGeoTrd2* trapezoid2 = dynamic_cast<const TGeoTrd2*>(&tgShape);
0303   if (trapezoid2 != nullptr) {
0304     if (!boost::istarts_with(axes, "X") &&
0305         std::abs(trapezoid2->GetDx1() - trapezoid2->GetDx2()) > s_epsilon) {
0306       throw std::invalid_argument(
0307           "TGeoTrd2 -> PlaneSurface: dx1 must be be equal to dx2 if not taken "
0308           "as trapezoidal side.");
0309     } else if (!boost::istarts_with(axes, "Y") &&
0310                std::abs(trapezoid2->GetDy1() - trapezoid2->GetDy2()) >
0311                    s_epsilon) {
0312       throw std::invalid_argument(
0313           "TGeoTrd2 -> PlaneSurface: dy1 must be be equal to dy2 if not taken "
0314           "as trapezoidal side.");
0315     }
0316     // Not allowed
0317     if (boost::istarts_with(axes, "XY") || boost::istarts_with(axes, "YX")) {
0318       throw std::invalid_argument(
0319           "TGeoTrd2 -> PlaneSurface: only works with (x/X)(z/Z) and "
0320           "(y/Y)(z/Z).");
0321     }
0322   }
0323 
0324   // Check if it's a Arb8
0325   const TGeoArb8* polygon8c = dynamic_cast<const TGeoArb8*>(&tgShape);
0326   TGeoArb8* polygon8 = nullptr;
0327   if (polygon8c != nullptr) {
0328     // Needed otherwise you can access GetVertices
0329     polygon8 = const_cast<TGeoArb8*>(polygon8c);
0330   }
0331 
0332   if ((polygon8c != nullptr) &&
0333       !(boost::istarts_with(axes, "XY") || boost::istarts_with(axes, "YX"))) {
0334     throw std::invalid_argument(
0335         "TGeoArb8 -> PlaneSurface: dz must be normal component of Surface.");
0336   }
0337 
0338   // The thickness will be filled
0339   double thickness = 0.;
0340 
0341   // The sign of the axes
0342   int xs = std::islower(axes.at(0)) != 0 ? -1 : 1;
0343   int ys = std::islower(axes.at(1)) != 0 ? -1 : 1;
0344 
0345   // Set up the columns : only cyclic iterations are allowed
0346   Vector3 cx = xs * ax;
0347   Vector3 cy = ys * ay;
0348   if (boost::istarts_with(axes, "XY")) {
0349     if (trapezoid2 != nullptr) {
0350       double dx1 = (ys < 0) ? trapezoid1->GetDx2() : trapezoid1->GetDx1();
0351       double dx2 = (ys < 0) ? trapezoid1->GetDx1() : trapezoid1->GetDx2();
0352       bounds = std::make_shared<const TrapezoidBounds>(
0353           scalor * dx1, scalor * dx2, scalor * trapezoid2->GetDy1());
0354       thickness = 2 * scalor * trapezoid2->GetDz();
0355     } else if (polygon8 != nullptr) {
0356       Double_t* tgverts = polygon8->GetVertices();
0357       std::vector<Vector2> pVertices;
0358       for (unsigned int ivtx = 0; ivtx < 4; ++ivtx) {
0359         pVertices.push_back(Vector2(scalor * xs * tgverts[ivtx * 2],
0360                                     scalor * ys * tgverts[ivtx * 2 + 1]));
0361       }
0362       bounds = std::make_shared<ConvexPolygonBounds<4>>(pVertices);
0363       thickness = 2 * scalor * polygon8->GetDz();
0364     } else if (box != nullptr) {
0365       bounds = std::make_shared<const RectangleBounds>(scalor * box->GetDX(),
0366                                                        scalor * box->GetDY());
0367       thickness = 2 * scalor * box->GetDZ();
0368     }
0369   } else if (boost::istarts_with(axes, "YZ")) {
0370     cx = xs * ay;
0371     cy = ys * az;
0372     if (trapezoid1 != nullptr) {
0373       throw std::invalid_argument(
0374           "TGeoTrd1 can only be converted with '(x/X)(z/Z)(y/Y)' axes");
0375     } else if (trapezoid2 != nullptr) {
0376       double dx1 = (ys < 0) ? trapezoid2->GetDy2() : trapezoid2->GetDy1();
0377       double dx2 = (ys < 0) ? trapezoid2->GetDy1() : trapezoid2->GetDy2();
0378       bounds = std::make_shared<const TrapezoidBounds>(
0379           scalor * dx1, scalor * dx2, scalor * trapezoid2->GetDz());
0380       thickness = 2 * scalor * trapezoid2->GetDx1();
0381     } else if (box != nullptr) {
0382       bounds = std::make_shared<const RectangleBounds>(scalor * box->GetDY(),
0383                                                        scalor * box->GetDZ());
0384       thickness = 2 * scalor * box->GetDX();
0385     }
0386   } else if (boost::istarts_with(axes, "ZX")) {
0387     cx = xs * az;
0388     cy = ys * ax;
0389     if (box != nullptr) {
0390       bounds = std::make_shared<const RectangleBounds>(scalor * box->GetDZ(),
0391                                                        scalor * box->GetDX());
0392       thickness = 2 * scalor * box->GetDY();
0393     }
0394   } else if (boost::istarts_with(axes, "XZ")) {
0395     cx = xs * ax;
0396     cy = ys * az;
0397     if (trapezoid1 != nullptr) {
0398       double dx1 = (ys < 0) ? trapezoid1->GetDx2() : trapezoid1->GetDx1();
0399       double dx2 = (ys < 0) ? trapezoid1->GetDx1() : trapezoid1->GetDx2();
0400       bounds = std::make_shared<const TrapezoidBounds>(
0401           scalor * dx1, scalor * dx2, scalor * trapezoid1->GetDz());
0402       thickness = 2 * scalor * trapezoid1->GetDy();
0403     } else if (trapezoid2 != nullptr) {
0404       double dx1 = (ys < 0) ? trapezoid2->GetDx2() : trapezoid2->GetDx1();
0405       double dx2 = (ys < 0) ? trapezoid2->GetDx1() : trapezoid2->GetDx2();
0406       bounds = std::make_shared<const TrapezoidBounds>(
0407           scalor * dx1, scalor * dx2, scalor * trapezoid2->GetDz());
0408       thickness = 2 * scalor * trapezoid2->GetDy1();
0409     } else if (box != nullptr) {
0410       bounds = std::make_shared<const RectangleBounds>(scalor * box->GetDX(),
0411                                                        scalor * box->GetDZ());
0412       thickness = 2 * scalor * box->GetDY();
0413     }
0414   } else if (boost::istarts_with(axes, "YX")) {
0415     cx = xs * ay;
0416     cy = ys * ax;
0417     if (trapezoid2 != nullptr) {
0418       double dx1 = (ys < 0) ? trapezoid2->GetDy2() : trapezoid2->GetDy1();
0419       double dx2 = (ys < 0) ? trapezoid2->GetDy1() : trapezoid2->GetDy2();
0420       bounds = std::make_shared<const TrapezoidBounds>(
0421           scalor * dx1, scalor * dx2, scalor * trapezoid2->GetDx1());
0422       thickness = 2 * scalor * trapezoid2->GetDz();
0423     } else if (polygon8 != nullptr) {
0424       const Double_t* tgverts = polygon8->GetVertices();
0425       std::vector<Vector2> pVertices;
0426       for (unsigned int ivtx = 0; ivtx < 4; ++ivtx) {
0427         pVertices.push_back(Vector2(scalor * xs * tgverts[ivtx * 2 + 1],
0428                                     scalor * ys * tgverts[ivtx * 2]));
0429       }
0430       bounds = std::make_shared<ConvexPolygonBounds<4>>(pVertices);
0431       thickness = 2 * scalor * polygon8->GetDz();
0432     } else if (box != nullptr) {
0433       bounds = std::make_shared<const RectangleBounds>(scalor * box->GetDY(),
0434                                                        scalor * box->GetDX());
0435       thickness = 2 * scalor * box->GetDZ();
0436     }
0437   } else if (boost::istarts_with(axes, "ZY")) {
0438     cx = xs * az;
0439     cy = ys * ay;
0440     if (box != nullptr) {
0441       bounds = std::make_shared<const RectangleBounds>(scalor * box->GetDZ(),
0442                                                        scalor * box->GetDY());
0443       thickness = 2 * scalor * box->GetDX();
0444     }
0445   } else {
0446     throw std::invalid_argument(
0447         "TGeoConverter: axes definition must be permutation of "
0448         "'(x/X)(y/Y)(z/Z)'");
0449   }
0450 
0451   // Create the normal vector & the transform
0452   auto cz = cx.cross(cy);
0453   auto transform = TGeoPrimitivesHelper::makeTransform(cx, cy, cz, t);
0454 
0455   return {bounds, transform, thickness};
0456 }
0457 
0458 std::tuple<std::shared_ptr<Acts::Surface>, Acts::ActsScalar>
0459 Acts::TGeoSurfaceConverter::toSurface(const TGeoShape& tgShape,
0460                                       const TGeoMatrix& tgMatrix,
0461                                       const std::string& axes,
0462                                       double scalor) noexcept(false) {
0463   // Get the placement and orientation in respect to its mother
0464   const Double_t* rotation = tgMatrix.GetRotationMatrix();
0465   const Double_t* translation = tgMatrix.GetTranslation();
0466 
0467   auto [cBounds, cTransform, cThickness] =
0468       cylinderComponents(tgShape, rotation, translation, axes, scalor);
0469   if (cBounds != nullptr) {
0470     return {Surface::makeShared<CylinderSurface>(cTransform, cBounds),
0471             cThickness};
0472   }
0473 
0474   auto [dBounds, dTransform, dThickness] =
0475       discComponents(tgShape, rotation, translation, axes, scalor);
0476   if (dBounds != nullptr) {
0477     return {Surface::makeShared<DiscSurface>(dTransform, dBounds), dThickness};
0478   }
0479 
0480   auto [pBounds, pTransform, pThickness] =
0481       planeComponents(tgShape, rotation, translation, axes, scalor);
0482   if (pBounds != nullptr) {
0483     return {Surface::makeShared<PlaneSurface>(pTransform, pBounds), pThickness};
0484   }
0485 
0486   return {nullptr, 0.};
0487 }