ATLAS Offline Software
Loading...
Searching...
No Matches
NnClusterizationFactory.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
18
19
20
21#include "GaudiKernel/ITHistSvc.h"
24#include <onnxruntime_cxx_api.h>
26
27//for position estimate and clustering
35
36//get std::isnan()
37#include <cmath>
38#include <algorithm>
39#include <limits>
40
41namespace {
42 std::pair<int, bool>
43 coerceToIntRange(double v){
44 constexpr double minint = std::numeric_limits<int>::min();
45 constexpr double maxint = std::numeric_limits<int>::max();
46 auto d = std::clamp(v, minint, maxint);
47
48 return {static_cast<int>(d), d != v};
49 }
50
51 // Placeholder (mm) for an unset coordinate in correctedRMS.
52 constexpr double unsetPos = -100.;
53
54 // Nominal pixel phi pitch (mm) for the lwtnn X position error.
55 constexpr double legacyPhiPitch = 0.05;
56}
57
58
59namespace InDet {
60 const std::array<std::regex, NnClusterizationFactory::kNNetworkTypes>
62 std::regex("^NumberParticles(|/|_.*)$"),
63 std::regex("^ImpactPoints([0-9])P(|/|_.*)$"),
64 std::regex("^ImpactPointErrorsX([0-9])(|/|_.*)$"),
65 std::regex("^ImpactPointErrorsY([0-9])(|/|_.*)$"),
66 };
67
69 const std::string& n, const IInterface* p)
70 : AthAlgTool(name, n, p){
71 declareInterface<NnClusterizationFactory>(this);
72 }
73
75 ATH_CHECK(m_chargeDataKey.initialize());
78 if (m_doRunI) {
80 } else {
82 }
83 // =0 means invalid in the following, but later on the values will be decremented by one and they indicate the index in the NN collection
85 m_NNId.clear();
86 m_NNId.resize( kNNetworkTypes -1 ) ;
87 // map networks to element in network collection
88 unsigned int nn_id=0;
89 std::smatch match_result;
90 for(const std::string &nn_name : m_nnOrder) {
91 ++nn_id;
92 for (unsigned int network_i=0; network_i<kNNetworkTypes; ++network_i) {
93 if (std::regex_match( nn_name, match_result, m_nnNames[network_i])) {
94 if (network_i == kNumberParticlesNN) {
95 m_nParticleNNId = nn_id;
96 } else {
97 if (m_nParticleGroup[network_i]>0) {
98 if (m_nParticleGroup[network_i]>=match_result.size()) {
99 ATH_MSG_ERROR("Regex and match group of particle multiplicity do not coincide (groups=" << match_result.size()
100 << " n particle group=" << m_nParticleGroup[network_i]
101 << "; type=" << network_i << ")");
102 }
103 int n_particles=std::stoi( match_result[m_nParticleGroup[network_i]].str());
104 if (n_particles<=0 or static_cast<unsigned int>(n_particles)>m_maxSubClusters) {
105 ATH_MSG_ERROR( "Failed to extract number of clusters the NN is meant for. Got " << match_result[m_nParticleGroup[network_i]].str()
106 << " But this is not in the valid range 1..." << m_maxSubClusters);
107 return StatusCode::FAILURE;
108 }
109 if (static_cast<unsigned int>(n_particles)>=m_NNId[network_i-1].size()) {
110 m_NNId[network_i-1].resize( n_particles );
111 }
112 m_NNId[network_i-1][n_particles-1] = nn_id;
113 } else {
114 if (m_NNId[network_i-1].empty()) {
115 m_NNId[network_i-1].resize(1);
116 }
117 m_NNId[network_i-1][0] = nn_id;
118 }
119 }
120 }
121 }
122 }
123 // check whether the NN IDs are all valid
124 // if valid decrease IDs by 1, because the ID is used as index in the NN collection.
125 if ((m_nParticleNNId==0) or (m_nParticleNNId>=m_nnOrder.size())) {
126 ATH_MSG_ERROR( "No NN specified to estimate the number of particles.");
127 return StatusCode::FAILURE;
128 }
130 ATH_MSG_VERBOSE("Expect NN " << s_nnTypeNames[0] << " at index " << m_nParticleNNId );
131 unsigned int type_i=0;
132 for (std::vector<unsigned int> &nn_id : m_NNId) {
133 ++type_i;
134 if (nn_id.empty()) {
135 ATH_MSG_ERROR( "No " << s_nnTypeNames[type_i] << " specified.");
136 return StatusCode::FAILURE;
137 }
138 if (m_nParticleGroup[type_i-1]>0 and nn_id.size() != m_maxSubClusters) {
139 ATH_MSG_ERROR( "Number of networks of type " << s_nnTypeNames[type_i] << " does match the maximum number of supported sub clusters " << m_maxSubClusters);
140 return StatusCode::FAILURE;
141 }
142 unsigned int n_particles=0;
143 for (unsigned int &a_nn_id : nn_id ) {
144 ++n_particles;
145 if ((a_nn_id==0) or (a_nn_id>m_nnOrder.size())) {
146 ATH_MSG_ERROR( "No " << s_nnTypeNames[type_i] << " specified for " << n_particles);
147 return StatusCode::FAILURE;
148 }
149 --a_nn_id;
150 ATH_MSG_VERBOSE("Expect NN " << s_nnTypeNames[type_i] << " for " << n_particles << " particle(s) at index " << a_nn_id );
151 }
152 }
153 // The X pitches are only fed to the ONNX network; the lwtnn and TTN
154 // variable orders have no slot for them.
155 if (m_useXPitches && !m_useONNX) {
156 ATH_MSG_FATAL("useXPitches=True is only supported with the ONNX backend "
157 "(useONNX=True): the lwtnn VariableOrder and the "
158 "TTrainedNetwork inputs do not include the X pitches.");
159 return StatusCode::FAILURE;
160 }
161 ATH_CHECK( m_readKeyWithoutTrack.initialize( !m_readKeyWithoutTrack.key().empty() ) );
162 ATH_CHECK( m_readKeyWithTrack.initialize( !m_readKeyWithTrack.key().empty() ) );
163 ATH_CHECK( m_readKeyJSON.initialize( !m_readKeyJSON.key().empty() ) );
164 ATH_CHECK( m_readKeyONNX.initialize( !m_readKeyONNX.key().empty() ) );
165 return StatusCode::SUCCESS;
166 }
167
168
169 std::vector<double>
171 const auto vectorSize{calculateVectorDimension(input.useTrackInfo)};
172 const auto invalidValue{std::numeric_limits<double>::quiet_NaN()};
173 std::vector<double> inputData(vectorSize, invalidValue);
174 size_t vectorIndex{0};
175 for (unsigned int u=0;u<m_sizeX;u++){
176 for (unsigned int s=0;s<m_sizeY;s++){
177 inputData[vectorIndex++] = input.matrixOfToT[u][s];
178 }
179 }
180 for (unsigned int s=0;s<m_sizeY;s++){
181 inputData[vectorIndex++] = input.vectorOfPitchesY[s];
182 }
183 inputData[vectorIndex++] = input.ClusterPixLayer;
184 inputData[vectorIndex++] = input.ClusterPixBarrelEC;
185 inputData[vectorIndex++] = input.phi;
186 inputData[vectorIndex++] = input.theta;
187 if (not input.useTrackInfo) inputData[vectorIndex] = input.etaModule;
188 return inputData;
189 }
190
191 std::vector<double>
193 const auto vectorSize{calculateVectorDimension(input.useTrackInfo)};
194 const auto invalidValue{std::numeric_limits<double>::quiet_NaN()};
195 std::vector<double> inputData(vectorSize, invalidValue);
196 size_t vectorIndex{0};
197 for (unsigned int u=0;u<m_sizeX;u++){
198 for (unsigned int s=0;s<m_sizeY;s++){
199 if (m_useToT){
200 inputData[vectorIndex++] = norm_rawToT(input.matrixOfToT[u][s]);
201 } else {
202 inputData[vectorIndex++] = norm_ToT(input.matrixOfToT[u][s]);
203 }
204 }
205 }
206 for (unsigned int s=0;s<m_sizeY;s++){
207 const double rawPitch(input.vectorOfPitchesY[s]);
208 const double normPitch(norm_pitch(rawPitch,m_addIBL));
209 if (std::isnan(normPitch)){
210 ATH_MSG_ERROR("NaN returned from norm_pitch, rawPitch = "<<rawPitch<<" addIBL = "<<m_addIBL);
211 }
212 inputData[vectorIndex++] = normPitch;
213 }
214 inputData[vectorIndex++] = norm_layerNumber(input.ClusterPixLayer);
215 inputData[vectorIndex++] = norm_layerType(input.ClusterPixBarrelEC);
216 if (input.useTrackInfo){
217 inputData[vectorIndex++] = norm_phi(input.phi);
218 inputData[vectorIndex] = norm_theta(input.theta);
219 } else {
220 inputData[vectorIndex++] = norm_phiBS(input.phi);
221 inputData[vectorIndex++] = norm_thetaBS(input.theta);
222 inputData[vectorIndex] = norm_etaModule(input.etaModule);
223 }
224 return inputData;
225 }
226
229 // Input layout: m_sizeX x m_sizeY ToT values, m_sizeY y pitches, the
230 // detector location and the track incidence angles. The no-track lwtnn
231 // networks also take eta module as a trailing value; the ONNX networks do
232 // not. When m_useXPitches is set, the ONNX layout adds the m_sizeX x pitches
233 // after the y pitches, for a matching (60 + m_sizeX)-input model.
234 const bool appendEtaModule{!m_useONNX && !input.useTrackInfo};
235 const auto vecSize{(m_useONNX ? calculateVectorDimension(true)
236 : calculateVectorDimension(input.useTrackInfo))
237 + (m_useXPitches ? m_sizeX.value() : 0u)};
238 Eigen::VectorXd valuesVector( vecSize );
239 // Fill it!
240 // Variable names here need to match the ones in the configuration...
241 // ...IN THE SAME ORDER!!!
242 // location in eigen matrix object where next element goes
243 int location(0);
244 for (const auto & xvec: input.matrixOfToT){
245 for (const auto & xyElement : xvec){
246 valuesVector[location++] = xyElement;
247 }
248 }
249 for (const auto & pitch : input.vectorOfPitchesY) {
250 valuesVector[location++] = pitch;
251 }
252 if (m_useXPitches) {
253 for (const auto & pitch : input.vectorOfPitchesX) {
254 valuesVector[location++] = pitch;
255 }
256 }
257 valuesVector[location] = input.ClusterPixLayer;
258 location++;
259 valuesVector[location] = input.ClusterPixBarrelEC;
260 location++;
261 valuesVector[location] = input.phi;
262 location++;
263 valuesVector[location] = input.theta;
264 location++;
265 if (appendEtaModule) {
266 valuesVector[location] = input.etaModule;
267 location++;
268 }
269 // We have only one node for now, so we just store things there.
270 // Format for use with lwtnn
271 std::vector<Eigen::VectorXd> vectorOfEigen;
272 vectorOfEigen.push_back(std::move(valuesVector));
273 return vectorOfEigen;
274 }
275
276 std::vector<double>
278 Amg::Vector3D & beamSpotPosition) const{
279 double tanl=0;
280 NNinput input( createInput(pCluster,beamSpotPosition,tanl) );
281 if (!input) return {};
282 // If using old TTrainedNetworks, fetch correct ones for the
283 // without-track situation and call them now.
285 const std::vector<double> & inputData=(this->*m_assembleInput)(input);
287 if (!nn_collection.isValid()) {
288 ATH_MSG_FATAL( "Failed to get trained network collection with key " << m_readKeyWithoutTrack.key() );
289 return {};
290 }
291 return estimateNumberOfParticlesTTN(**nn_collection, inputData);
292 }
293 // Otherwise, prepare input vector and use ONNX or LWTNN networks.
295 if (m_useONNX) {
296 return estimateNumberOfParticlesONNX(nnInputVector[0]);
297 }
298 return estimateNumberOfParticlesLWTNN(nnInputVector);
299 }
300
301 std::vector<double>
303 const Trk::Surface& pixelSurface,
304 const Trk::TrackParameters& trackParsAtSurface) const{
305 Amg::Vector3D dummyBS(0,0,0);
306 double tanl=0;
307 NNinput input( createInput(pCluster,dummyBS,tanl) );
308
309 if (!input) return {};
310 addTrackInfoToInput(input,pixelSurface,trackParsAtSurface,tanl);
311 std::vector<double> inputData=(this->*m_assembleInput)(input);
312 // If using old TTrainedNetworks, fetch correct ones for the
313 // with-track situation and call them now.
316 if (!nn_collection.isValid()) {
317 ATH_MSG_FATAL( "Failed to get trained network collection with key " << m_readKeyWithoutTrack.key() );
318 return {};
319 }
320 return estimateNumberOfParticlesTTN(**nn_collection, inputData);
321 }
322 // Otherwise, prepare input vector and use ONNX or LWTNN networks.
324 if (m_useONNX) {
325 return estimateNumberOfParticlesONNX(nnInputVector[0]);
326 }
327 return estimateNumberOfParticlesLWTNN(nnInputVector);
328 }
329
330 std::vector<double>
332 const std::vector<double>& inputData) const{
333 ATH_MSG_DEBUG("Using TTN number network");
334 std::vector<double> resultNN_TTN{};
335 if (not (m_nParticleNNId < nn_collection.size())){ //note: m_nParticleNNId is unsigned
336 ATH_MSG_FATAL("NnClusterizationFactory::estimateNumberOfParticlesTTN: Index "<<m_nParticleNNId<< "is out of range.");
337 return resultNN_TTN;
338 }
339 auto *const pNetwork = nn_collection[m_nParticleNNId].get();
340 if (not pNetwork){
341 ATH_MSG_FATAL("NnClusterizationFactory::estimateNumberOfParticlesTTN: nullptr returned for TrainedNetwork");
342 return resultNN_TTN;
343 }
344 // dereference unique_ptr<TTrainedNetwork> then call calculateOutput :
345 resultNN_TTN = (*pNetwork.*m_calculateOutput)(inputData);
346 ATH_MSG_VERBOSE(" TTN Prob of n. particles (1): " << resultNN_TTN[0] <<
347 " (2): " << resultNN_TTN[1] <<
348 " (3): " << resultNN_TTN[2]);
349 return resultNN_TTN;
350 }
351
352
353 std::vector<double>
355 std::vector<double> result(3,0.0);//ok as invalid result?
357 if (!lwtnn_collection.isValid()) {
358 ATH_MSG_FATAL( "Failed to get LWTNN network collection with key " << m_readKeyJSON.key() );
359 return result;
360 }
361 if (lwtnn_collection->empty()){
362 ATH_MSG_FATAL( "LWTNN network collection with key " << m_readKeyJSON.key()<<" is empty." );
363 return result;
364 }
365 ATH_MSG_DEBUG("Using lwtnn number network");
366 // Order of output matches order in JSON config in "outputs"
367 // Only 1 node here, simple compute function
368 Eigen::VectorXd discriminant = lwtnn_collection->at(0)->compute(input);
369 const double & num0 = discriminant[0];
370 const double & num1 = discriminant[1];
371 const double & num2 = discriminant[2];
372 // Get normalized predictions
373 const auto inverseSum = 1./(num0+num1+num2);
374 result[0] = num0 * inverseSum;
375 result[1] = num1 * inverseSum;
376 result[2] = num2 * inverseSum;
377 ATH_MSG_VERBOSE(" LWTNN Prob of n. particles (1): " << result[0] <<
378 " (2): " << result[1] <<
379 " (3): " << result[2]);
380 return result;
381 }
382
383
384 std::vector<Amg::Vector2D>
386 Amg::Vector3D & beamSpotPosition,
387 std::vector<Amg::MatrixX> & errors,
388 int numberSubClusters) const{
389 ATH_MSG_VERBOSE(" Starting to estimate positions...");
390 double tanl=0;
391 NNinput input( createInput(pCluster,beamSpotPosition,tanl) );
392 if (!input){
393 return {};
394 }
395 // If using old TTrainedNetworks, fetch correct ones for the
396 // without-track situation and call them now.
398 const std::vector<double> & inputData=(this->*m_assembleInput)(input);
400 if (!nn_collection.isValid()) {
401 ATH_MSG_FATAL( "Failed to get trained network collection with key " << m_readKeyWithoutTrack.key() );
402 return {};
403 }
404 // *(ReadCondHandle<>) returns a pointer rather than a reference ...
405 return estimatePositionsTTN(**nn_collection, inputData,input,pCluster,numberSubClusters,errors);
406 }
407 // Otherwise, prepare input vector and use ONNX or LWTNN networks.
409 if (m_useONNX) {
410 return estimatePositionsONNX(nnInputVector[0],input,pCluster,numberSubClusters,errors);
411 }
412 return estimatePositionsLWTNN(nnInputVector,input,pCluster,numberSubClusters,errors);
413 }
414
415
416 std::vector<Amg::Vector2D>
418 const Trk::Surface& pixelSurface,
419 const Trk::TrackParameters& trackParsAtSurface,
420 std::vector<Amg::MatrixX> & errors,
421 int numberSubClusters) const{
422 ATH_MSG_VERBOSE(" Starting to estimate positions...");
423 Amg::Vector3D dummyBS(0,0,0);
424 double tanl=0;
425 NNinput input( createInput(pCluster, dummyBS, tanl) );
426 if (!input) return {};
427 addTrackInfoToInput(input,pixelSurface,trackParsAtSurface,tanl);
428 // If using old TTrainedNetworks, fetch correct ones for the
429 // without-track situation and call them now.
431 std::vector<double> inputData=(this->*m_assembleInput)(input);
433 if (!nn_collection.isValid()) {
434 ATH_MSG_FATAL( "Failed to get trained network collection with key " << m_readKeyWithTrack.key() );
435 return {};
436 }
437 return estimatePositionsTTN(**nn_collection, inputData,input,pCluster,numberSubClusters,errors);
438 }
439 // Otherwise, prepare input vector and use ONNX or LWTNN networks.
441 if (m_useONNX) {
442 return estimatePositionsONNX(nnInputVector[0],input,pCluster,numberSubClusters,errors);
443 }
444 return estimatePositionsLWTNN(nnInputVector,input,pCluster,numberSubClusters,errors);
445 }
446
447 std::vector<Amg::Vector2D>
449 const std::vector<double>& inputData,
450 const NNinput& input,
451 const InDet::PixelCluster& pCluster,
452 int numberSubClusters,
453 std::vector<Amg::MatrixX> & errors) const{
454 bool applyRecentering=(!input.useTrackInfo and m_useRecenteringNNWithouTracks) or (input.useTrackInfo and m_useRecenteringNNWithTracks);
455 std::vector<Amg::Vector2D> allPositions{};
456 const auto endNnIdx = nn_collection.size();
457 if (numberSubClusters>0 and static_cast<unsigned int>(numberSubClusters) < m_maxSubClusters) {
458 const auto subClusterIndex = numberSubClusters-1;
459 // get position network id for the given cluster multiplicity then
460 // dereference unique_ptr<TTrainedNetwork> then call calculateOutput :
461 const auto networkIndex = m_NNId[kPositionNN-1].at(subClusterIndex);
462 //TTrainedNetworkCollection inherits from std::vector
463 if (not(networkIndex < endNnIdx)){
464 ATH_MSG_FATAL("estimatePositionsTTN: Requested collection index, "<< networkIndex << " is out of range.");
465 return allPositions;
466 }
467 auto *const pNetwork = nn_collection[networkIndex].get();
468 std::vector<double> position1P = (*pNetwork.*m_calculateOutput)(inputData);
469 std::vector<Amg::Vector2D> myPosition1=getPositionsFromOutput(position1P,input,pCluster);
470 assert( position1P.size() % 2 == 0);
471 for (unsigned int i=0; i<position1P.size()/2 ; ++i) {
472 ATH_MSG_DEBUG(" Original RAW Estimated positions (" << i << ") x: " << back_posX(position1P[0+i*2],applyRecentering) << " y: " << back_posY(position1P[1+i*2]));
473 ATH_MSG_DEBUG(" Original estimated myPositions (" << i << ") x: " << myPosition1[i][Trk::locX] << " y: " << myPosition1[i][Trk::locY]);
474 }
475 const std::size_t nPositions{static_cast<std::size_t>(numberSubClusters*2)};
476 assert( nPositions <= position1P.size() );
477 //reserve space before copy, to avoid reallocation
478 std::vector<double> inputDataNew(inputData);
479 inputDataNew.reserve( inputData.size() + nPositions);
480 inputDataNew.insert(inputDataNew.end(), position1P.begin(), position1P.begin() + nPositions);
481
482 // get error network id for the given cluster multiplicity then
483 // dereference unique_ptr<TTrainedNetwork> then call calculateOutput :
484 const auto xNetworkIndex = m_NNId[kErrorXNN-1].at(subClusterIndex);
485 const auto yNetworkIndex = m_NNId[kErrorYNN-1].at(subClusterIndex);
486 if ((not (xNetworkIndex < endNnIdx)) or (not (yNetworkIndex < endNnIdx))){
487 ATH_MSG_FATAL("estimatePositionsTTN: A requested collection index, "<< xNetworkIndex << " or "<< yNetworkIndex << "is out of range.");
488 return allPositions;
489 }
490 auto *pxNetwork = nn_collection.at(xNetworkIndex).get();
491 auto *pyNetwork = nn_collection.at(yNetworkIndex).get();
492 //call the selected member function of the TTrainedNetwork
493 std::vector<double> errors1PX = (*pxNetwork.*m_calculateOutput)(inputDataNew);
494 std::vector<double> errors1PY = (*pyNetwork.*m_calculateOutput)(inputDataNew);
495 //
496 std::vector<Amg::MatrixX> errorMatrices1;
497 getErrorMatrixFromOutput(errors1PX,errors1PY,errorMatrices1,numberSubClusters);
498 allPositions.reserve( allPositions.size() + myPosition1.size());
499 errors.reserve( errors.size() + myPosition1.size());
500 for (unsigned int i=0;i<myPosition1.size();i++){
501 allPositions.push_back(myPosition1[i]);
502 errors.push_back(errorMatrices1[i]);
503 }
504 }
505 return allPositions;
506 }
507
508
509 std::vector<Amg::Vector2D>
511 NNinput& rawInput,
512 const InDet::PixelCluster& pCluster,
513 int numberSubClusters,
514 std::vector<Amg::MatrixX> & errors) const {
516 if (not lwtnn_collection.isValid()) {
517 ATH_MSG_FATAL( "Failed to get LWTNN network collection with key " << m_readKeyJSON.key() );
518 return {};
519 }
520 if (lwtnn_collection->empty()){
521 ATH_MSG_FATAL( "estimatePositionsLWTNN: LWTNN network collection with key " << m_readKeyJSON.key()<<" is empty." );
522 return {};
523 }
524 // Need to evaluate the correct network once per cluster we're interested in.
525 // Save the output
526 std::vector<double> positionValues{};
527 std::vector<Amg::MatrixX> errorMatrices;
528 errorMatrices.reserve(numberSubClusters);
529 positionValues.reserve(numberSubClusters * 2);
530 std::size_t outputNode(0);
531 for (int cluster = 1; cluster < numberSubClusters+1; cluster++) {
532 // Check that the network is defined.
533 // If not, we are outside an IOV and should fail
534 const auto pNetwork = lwtnn_collection->find(numberSubClusters);
535 const bool validGraph = (pNetwork != lwtnn_collection->end()) and (pNetwork->second != nullptr);
536 if (not validGraph) {
537 std::string infoMsg ="Acceptable numbers of subclusters for the lwtnn collection:\n ";
538 for (const auto & pair: **lwtnn_collection){
539 infoMsg += std::to_string(pair.first) + "\n ";
540 }
541 infoMsg += "\nNumber of subclusters requested : "+ std::to_string(numberSubClusters);
542 ATH_MSG_DEBUG(infoMsg);
543 ATH_MSG_FATAL( "estimatePositionsLWTNN: No lwtnn network found for the number of clusters.\n"
544 <<" If you are outside the valid range for an lwtnn-based configuration, please run with useNNTTrainedNetworks instead.\n Key = "
545 << m_readKeyJSON.key() );
546 return {};
547 }
548 if(numberSubClusters==1) {
549 outputNode = m_outputNodesPos1;
550 } else if(numberSubClusters==2) {
551 outputNode = m_outputNodesPos2[cluster-1];
552 } else if(numberSubClusters==3) {
553 outputNode = m_outputNodesPos3[cluster-1];
554 } else {
555 ATH_MSG_FATAL( "Cannot evaluate LWTNN networks with " << numberSubClusters << " numberSubClusters" );
556 return {};
557 }
558
559 // Order of output matches order in JSON config in "outputs"
560 // "alpha", "mean_x", "mean_y", "prec_x", "prec_y"
561 // Assume here that 1 particle network is in position 1, 2 at 2, and 3 at 3.
562 Eigen::VectorXd position = lwtnn_collection->at(numberSubClusters)->compute(input, {}, outputNode);
563 ATH_MSG_DEBUG("Testing for numberSubClusters " << numberSubClusters << " and cluster " << cluster);
564 for (int i=0; i<position.rows(); i++) {
565 ATH_MSG_DEBUG(" position " << position[i]);
566 }
567 positionValues.push_back(position[1]); //mean_x
568 positionValues.push_back(position[2]); //mean_y
569 // Fill errors.
570 // Values returned by NN are inverse of variance, and we want variances.
571 const float rawRmsX = std::sqrt(1.0/position[3]); //prec_x
572 const float rawRmsY = std::sqrt(1.0/position[4]); //prec_y
573 // Convert to real space units: x uses the nominal pixel pitch, y
574 // integrates the actual pitches. (estimatePositionsONNX uses correctedRMS
575 // for both directions.)
576 const double rmsX = rawRmsX * legacyPhiPitch;
577 const double rmsY = correctedRMS(rawRmsY, rawInput.vectorOfPitchesY, m_sizeY);
578 ATH_MSG_DEBUG(" Estimated RMS errors (1) x: " << rmsX << ", y: " << rmsY);
579 // Fill matrix
580 Amg::MatrixX erm(2,2);
581 erm.setZero();
582 erm(0,0)=rmsX*rmsX;
583 erm(1,1)=rmsY*rmsY;
584 errorMatrices.push_back(std::move(erm));
585 }
586 std::vector<Amg::Vector2D> myPositions = getPositionsFromOutput(positionValues,rawInput,pCluster);
587 ATH_MSG_DEBUG(" Estimated myPositions (1) x: " << myPositions[0][Trk::locX] << " y: " << myPositions[0][Trk::locY]);
588 errors=std::move(errorMatrices);
589 return myPositions;
590 }
591
592 double
594 const std::vector<float>& pitches,
595 unsigned int size) const{
596 // Convert a pixel-unit RMS to a distance by integrating the actual pitches,
597 // so non-uniform pitch (ITk long/end pixels, 25 um modules) is handled.
598 // size is m_sizeX in phi (x), m_sizeY in eta (y).
599 double p = posPixels + (size - 1) * 0.5;
600 double p_pos = unsetPos;
601 double p_center = unsetPos;
602 double p_actual = 0;
603 for (unsigned int i = 0; i < size; i++) {
604 if (p >= i and p <= (i + 1)) p_pos = p_actual + (p - i + 0.5) * pitches.at(i);
605 if (i == (size - 1) / 2) p_center = p_actual + 0.5 * pitches.at(i);
606 p_actual += pitches.at(i);
607 }
608 return std::abs(p_pos - p_center);
609 }
610
611 void
613 std::vector<double>& outputY,
614 std::vector<Amg::MatrixX>& errorMatrix,
615 int nParticles) const{
616 int sizeOutputX=outputX.size()/nParticles;
617 int sizeOutputY=outputY.size()/nParticles;
618 double minimumX=-errorHalfIntervalX(nParticles);
619 double maximumX=errorHalfIntervalX(nParticles);
620 double minimumY=-errorHalfIntervalY(nParticles);
621 double maximumY=errorHalfIntervalY(nParticles);
622 //X=0...sizeOutput-1
623 //Y=minimum+(maximum-minimum)/sizeOutput*(X+1./2.)
624 errorMatrix.reserve( errorMatrix.size() + nParticles);
625 for (int i=0;i<nParticles;i++){
626 double sumValuesX=0;
627 for (int u=0;u<sizeOutputX;u++){
628 sumValuesX+=outputX[i*sizeOutputX+u];
629 }
630 double sumValuesY=0;
631 for (int u=0;u<sizeOutputY;u++){
632 sumValuesY+=outputY[i*sizeOutputY+u];
633 }
634 ATH_MSG_VERBOSE(" minimumX: " << minimumX << " maximumX: " << maximumX << " sizeOutputX " << sizeOutputX);
635 ATH_MSG_VERBOSE(" minimumY: " << minimumY << " maximumY: " << maximumY << " sizeOutputY " << sizeOutputY);
636 double RMSx=0;
637 for (int u=0;u<sizeOutputX;u++){
638 RMSx+=outputX[i*sizeOutputX+u]/sumValuesX*std::pow(minimumX+(maximumX-minimumX)/(double)(sizeOutputX-2)*(u-1./2.),2);
639 }
640 RMSx=std::sqrt(RMSx);//computed error!
641 ATH_MSG_VERBOSE(" first Iter RMSx: " << RMSx);
642 double intervalErrorX=3*RMSx;
643 //now recompute between -3*RMSx and +3*RMSx
644 int minBinX=(int)(1+(-intervalErrorX-minimumX)/(maximumX-minimumX)*(double)(sizeOutputX-2));
645 int maxBinX=(int)(1+(intervalErrorX-minimumX)/(maximumX-minimumX)*(double)(sizeOutputX-2));
646 if (maxBinX>sizeOutputX-1) maxBinX=sizeOutputX-1;
647 if (minBinX<0) minBinX=0;
648 ATH_MSG_VERBOSE(" minBinX: " << minBinX << " maxBinX: " << maxBinX );
649 RMSx=0;
650 for (int u=minBinX;u<maxBinX+1;u++){
651 RMSx+=outputX[i*sizeOutputX+u]/sumValuesX*std::pow(minimumX+(maximumX-minimumX)/(double)(sizeOutputX-2)*(u-1./2.),2);
652 }
653 RMSx=std::sqrt(RMSx);//computed error!
654 double RMSy=0;
655 for (int u=0;u<sizeOutputY;u++){
656 RMSy+=outputY[i*sizeOutputY+u]/sumValuesY*std::pow(minimumY+(maximumY-minimumY)/(double)(sizeOutputY-2)*(u-1./2.),2);
657 }
658 RMSy=std::sqrt(RMSy);//computed error!
659 ATH_MSG_VERBOSE("first Iter RMSy: " << RMSy );
660 double intervalErrorY=3*RMSy;
661 //now recompute between -3*RMSy and +3*RMSy
662 int minBinY=(int)(1+(-intervalErrorY-minimumY)/(maximumY-minimumY)*(double)(sizeOutputY-2));
663 int maxBinY=(int)(1+(intervalErrorY-minimumY)/(maximumY-minimumY)*(double)(sizeOutputY-2));
664 if (maxBinY>sizeOutputY-1) maxBinY=sizeOutputY-1;
665 if (minBinY<0) minBinY=0;
666 ATH_MSG_VERBOSE("minBinY: " << minBinY << " maxBinY: " << maxBinY );
667 RMSy=0;
668 for (int u=minBinY;u<maxBinY+1;u++){
669 RMSy+=outputY[i*sizeOutputY+u]/sumValuesY*std::pow(minimumY+(maximumY-minimumY)/(double)(sizeOutputY-2)*(u-1./2.),2);
670 }
671 RMSy=std::sqrt(RMSy);//computed error!
672 ATH_MSG_VERBOSE("Computed error, sigma(X) " << RMSx << " sigma(Y) " << RMSy );
673 Amg::MatrixX erm(2,2);
674 erm.setZero();
675 erm(0,0)=RMSx*RMSx;
676 erm(1,1)=RMSy*RMSy;
677 errorMatrix.push_back(std::move(erm));
678 }//end nParticles
679 }//getErrorMatrixFromOutput
680
681
682 std::vector<Amg::Vector2D>
684 const NNinput & input,
685 const InDet::PixelCluster& pCluster) const{
686 ATH_MSG_VERBOSE(" Translating output back into a position " );
687 const InDetDD::SiDetectorElement* element=pCluster.detectorElement();//DEFINE
688 const InDetDD::PixelModuleDesign* design
689 (dynamic_cast<const InDetDD::PixelModuleDesign*>(&element->design()));
690 if (not design){
691 ATH_MSG_ERROR("Dynamic cast failed at line "<<__LINE__<<" of NnClusterizationFactory.cxx.");
692 return {};
693 }
694 int numParticles=output.size()/2;
695 int columnWeightedPosition=input.columnWeightedPosition;
696 int rowWeightedPosition=input.rowWeightedPosition;
697 ATH_MSG_VERBOSE(" REF POS columnWeightedPos: " << columnWeightedPosition << " rowWeightedPos: " << rowWeightedPosition );
698 bool applyRecentering=false;
699 if (m_useRecenteringNNWithouTracks and (not input.useTrackInfo)){
700 applyRecentering=true;
701 }
702 if (m_useRecenteringNNWithTracks and input.useTrackInfo){
703 applyRecentering=true;
704 }
705 std::vector<Amg::Vector2D> positions;
706 for (int u=0;u<numParticles;u++){
707 double posXid{};
708 double posYid{};
709 if(m_doRunI){
710 posXid=back_posX(output[2*u],applyRecentering)+rowWeightedPosition;
711 posYid=back_posY(output[2*u+1])+columnWeightedPosition;
712 }else{
713 posXid=output[2*u]+rowWeightedPosition;
714 posYid=output[2*u+1]+columnWeightedPosition;
715 }
716 ATH_MSG_VERBOSE(" N. particle: " << u << " idx posX " << posXid << " posY " << posYid );
717 //ATLASRECTS-7155 : Pixel Charge Calibration needs investigating
718 const auto & [posXid_int, coercedX]=coerceToIntRange(posXid+0.5);
719 const auto & [posYid_int, coercedY]=coerceToIntRange(posYid+0.5);
720 if (coercedX or coercedY){
721 ATH_MSG_WARNING("X or Y position value has been limited in range; original values are (" << posXid<<", "<<posYid<<")");
722 //we cannot skip these values, it seems client code relies on the size of input vector and output vector being the same
723 }
724 ATH_MSG_VERBOSE(" N. particle: " << u << " TO INTEGER idx posX " << posXid_int << " posY " << posYid_int );
725 InDetDD::SiLocalPosition siLocalPositionDiscrete(design->positionFromColumnRow(posYid_int,posXid_int));
726 InDetDD::SiCellId cellIdOfPositionDiscrete=design->cellIdOfPosition(siLocalPositionDiscrete);
727 if ( not cellIdOfPositionDiscrete.isValid()){
728 ATH_MSG_WARNING(" Cell is outside validity region with index Y: " << posYid_int << " and index X: " << posXid_int << ". Not foreseen... " );
729 }
730 InDetDD::SiDiodesParameters diodeParameters = design->parameters(cellIdOfPositionDiscrete);
731 double pitchY = diodeParameters.width().xEta();
732 double pitchX = diodeParameters.width().xPhi();
733 ATH_MSG_VERBOSE(" Translated weighted position : " << siLocalPositionDiscrete.xPhi()
734 << " Translated weighted position : " << siLocalPositionDiscrete.xEta() );
735 //FOR TEST
736 InDetDD::SiLocalPosition siLocalPositionDiscreteOneRowMoreOneColumnMore(design->positionFromColumnRow(posYid_int+1,posXid_int+1));
737 ATH_MSG_VERBOSE(" Translated weighted position +1col +1row phi: " << siLocalPositionDiscreteOneRowMoreOneColumnMore.xPhi()
738 << " Translated weighted position +1col +1row eta: " << siLocalPositionDiscreteOneRowMoreOneColumnMore.xEta() );
739 ATH_MSG_VERBOSE("PitchY: " << pitchY << " pitchX " << pitchX );
740 InDetDD::SiLocalPosition siLocalPositionAdd(pitchY*(posYid-(double)posYid_int),
741 pitchX*(posXid-(double)posXid_int));
742 double lorentzShift=m_pixelLorentzAngleTool->getLorentzShift(element->identifyHash(), Gaudi::Hive::currentContext());
743 if (input.ClusterPixBarrelEC == 0){
744 if (not input.useTrackInfo){
746 } else {
748 }
749 }
750
752 siLocalPosition(siLocalPositionDiscrete.xEta()+pitchY*(posYid-(double)posYid_int),
753 siLocalPositionDiscrete.xPhi()+pitchX*(posXid-(double)posXid_int)+lorentzShift);
754 ATH_MSG_VERBOSE(" Translated final position phi: " << siLocalPosition.xPhi() << " eta: " << siLocalPosition.xEta() );
755 const auto halfWidth{design->width()*0.5};
756 if (siLocalPositionDiscrete.xPhi() > halfWidth){
757 siLocalPosition=InDetDD::SiLocalPosition(siLocalPositionDiscrete.xEta()+pitchY*(posYid-(double)posYid_int),
758 halfWidth-1e-6);
759 ATH_MSG_WARNING(" Corrected out of boundary cluster from x(phi): " << siLocalPositionDiscrete.xPhi()+pitchX*(posXid-(double)posXid_int)
760 << " to: " << halfWidth-1e-6);
761 } else if (siLocalPositionDiscrete.xPhi() < -halfWidth) {
762 siLocalPosition=InDetDD::SiLocalPosition(siLocalPositionDiscrete.xEta()+pitchY*(posYid-(double)posYid_int),
763 -halfWidth+1e-6);
764 ATH_MSG_WARNING(" Corrected out of boundary cluster from x(phi): " << siLocalPositionDiscrete.xPhi()+pitchX*(posXid-(double)posXid_int)
765 << " to: " << -halfWidth+1e-6);
766 }
767 positions.emplace_back(siLocalPosition);
768 }//iterate over all particles
769 return positions;
770 }
771
772
773 void
775 const Trk::Surface& pixelSurface, // pixelSurface = pcot->associatedSurface();
776 const Trk::TrackParameters& trackParsAtSurface,
777 const double tanl) const {
778 input.useTrackInfo=true;
779 Amg::Vector3D particleDir = trackParsAtSurface.momentum().unit();
780 Amg::Vector3D localIntersection = pixelSurface.transform().inverse().linear() * particleDir;
781 localIntersection *= 0.250/cos(localIntersection.theta());
782 float trackDeltaX = (float)localIntersection.x();
783 float trackDeltaY = (float)localIntersection.y();
784 input.theta=std::atan2(trackDeltaY,0.250);
785 input.phi=std::atan2(trackDeltaX,0.250);
786 ATH_MSG_VERBOSE("Angle phi bef Lorentz corr: " << input.phi );
787 input.phi=std::atan(std::tan(input.phi)-tanl);
788 ATH_MSG_VERBOSE(" From track: angle phi: " << input.phi << " theta: " << input.theta );
789 }
790
791
792 NNinput
794 Amg::Vector3D & beamSpotPosition,
795 double & tanl) const{
796 NNinput input;
797 ATH_MSG_VERBOSE(" Starting creating input from cluster " );
798 const InDetDD::SiDetectorElement* element=pCluster.detectorElement();
799 if (not element) {
800 ATH_MSG_ERROR("Could not get detector element");
801 return input;
802 }
803 const AtlasDetectorID* aid = element->getIdHelper();
804 if (not aid){
805 ATH_MSG_ERROR("Could not get ATLASDetectorID");
806 return input;
807 }
808
810 ATH_MSG_ERROR("Could not get PixelID pointer");
811 return input;
812 }
813 const PixelID* pixelIDp=static_cast<const PixelID*>(aid);
814 const PixelID& pixelID = *pixelIDp;
815 const InDetDD::PixelModuleDesign* design
816 (dynamic_cast<const InDetDD::PixelModuleDesign*>(&element->design()));
817 if (not design){
818 ATH_MSG_ERROR("Dynamic cast failed at line "<<__LINE__<<" of NnClusterizationFactory.cxx.");
819 return input;
820 }
822 const PixelChargeCalibCondData *calibData = *calibDataHandle;
823 const std::vector<Identifier>& rdos = pCluster.rdoList();
824 const size_t rdoSize = rdos.size();
825 ATH_MSG_VERBOSE(" Number of RDOs: " << rdoSize );
826 const std::vector<float>& chList = pCluster.chargeList();
827 const std::vector<int>& totList = pCluster.totList();
828 std::vector<float> chListRecreated{};
829 chListRecreated.reserve(rdoSize);
830 ATH_MSG_VERBOSE(" Number of charges: " << chList.size() );
831 std::vector<int>::const_iterator tot = totList.begin();
832 std::vector<Identifier>::const_iterator rdosBegin = rdos.begin();
833 std::vector<Identifier>::const_iterator rdosEnd = rdos.end();
834 std::vector<int> totListRecreated{};
835 totListRecreated.reserve(rdoSize);
836 std::vector<int>::const_iterator totRecreated = totListRecreated.begin();
837 // Recreate both charge list and ToT list to correct for the IBL ToT overflow (and later for small hits):
838 ATH_MSG_VERBOSE("Charge list is not filled ... re-creating it.");
839 IdentifierHash moduleHash = element->identifyHash(); // wafer hash
840
841 for ( ; rdosBegin!= rdosEnd and tot != totList.end(); ++tot, ++rdosBegin, ++totRecreated ){
842 // recreate the charge: should be a method of the calibSvc
843 int tot0 = *tot;
844 Identifier pixid = *rdosBegin;
845 assert( element->identifyHash() == pixelID.wafer_hash(pixelID.wafer_id(pixid)));
846
847 std::array<InDetDD::PixelDiodeTree::CellIndexType,2> diode_idx
849 pixelID.eta_index(pixid));
850 InDetDD::PixelDiodeTree::DiodeProxy si_param ( design->diodeProxyFromIdx(diode_idx));
851 std::uint32_t feValue = design->getFE(si_param);
852 auto diode_type = design->getDiodeType(si_param);
854 && design->numberOfConnectedCells( design->readoutIdOfCell(InDetDD::SiCellId(diode_idx[0],diode_idx[1])))>1) {
856 }
857
858 float charge = calibData->getCharge(diode_type, moduleHash, feValue, tot0);
859 chListRecreated.push_back(charge);
860 totListRecreated.push_back(tot0);
861 }
862 // reset the rdo iterator
863 rdosBegin = rdos.begin();
864 rdosEnd = rdos.end();
865 // and the tot iterator
866 tot = totList.begin();
867 totRecreated = totListRecreated.begin();
868 // Always use recreated charge and ToT lists:
869 std::vector<float>::const_iterator charge = chListRecreated.begin();
870 std::vector<float>::const_iterator chargeEnd = chListRecreated.end();
871 tot = totListRecreated.begin();
872 std::vector<int>::const_iterator totEnd = totListRecreated.end();
873 InDetDD::SiLocalPosition sumOfWeightedPositions(0,0,0);
874 double sumOfTot=0;
875 int rowMin = 999;
876 int rowMax = 0;
877 int colMin = 999;
878 int colMax = 0;
879 for (; (rdosBegin!= rdosEnd) and (charge != chargeEnd) and (tot != totEnd); ++rdosBegin, ++charge, ++tot){
880 Identifier rId = *rdosBegin;
881 int row = pixelID.phi_index(rId);
882 int col = pixelID.eta_index(rId);
883 InDetDD::SiLocalPosition siLocalPosition (design->positionFromColumnRow(col,row));
884 if (not m_useToT){
885 sumOfWeightedPositions += (*charge)*siLocalPosition;
886 sumOfTot += (*charge);
887 } else {
888 sumOfWeightedPositions += ((double)(*tot))*siLocalPosition;
889 sumOfTot += (double)(*tot);
890 }
891 rowMin = std::min(row, rowMin);
892 rowMax = std::max(row, rowMax);
893 colMin = std::min(col, colMin);
894 colMax = std::max(col, colMax);
895
896 }
897 sumOfWeightedPositions /= sumOfTot;
898 //what you want to know is simple:
899 //just the row and column of this average position!
900 InDetDD::SiCellId cellIdWeightedPosition=design->cellIdOfPosition(sumOfWeightedPositions);
901
902 if (!cellIdWeightedPosition.isValid()){
903 ATH_MSG_WARNING(" Weighted position is on invalid CellID." );
904 }
905 int columnWeightedPosition=cellIdWeightedPosition.etaIndex();
906 int rowWeightedPosition=cellIdWeightedPosition.phiIndex();
907 ATH_MSG_VERBOSE(" weighted pos row: " << rowWeightedPosition << " col: " << columnWeightedPosition );
908 int centralIndexX=(m_sizeX-1)/2;
909 int centralIndexY=(m_sizeY-1)/2;
910 if (std::abs(rowWeightedPosition-rowMin)>centralIndexX or
911 std::abs(rowWeightedPosition-rowMax)>centralIndexX){
912 ATH_MSG_VERBOSE(" Cluster too large rowMin" << rowMin << " rowMax " << rowMax << " centralX " << centralIndexX);
913 return input;
914 }
915 if (std::abs(columnWeightedPosition-colMin)>centralIndexY or
916 std::abs(columnWeightedPosition-colMax)>centralIndexY){
917 ATH_MSG_VERBOSE(" Cluster too large colMin" << colMin << " colMax " << colMax << " centralY " << centralIndexY);
918 return input;
919 }
920 input.matrixOfToT.reserve(m_sizeX);
921 for (unsigned int a=0;a<m_sizeX;a++){
922 input.matrixOfToT.emplace_back(m_sizeY, 0.0);
923 }
924 // Seed the pitches for cells with no hit. For ONNX (ITk) take the nominal
925 // from the design, so non-uniform sensors (e.g. 50x50 or 25x100 um) get the
926 // right value; for lwtnn keep the 0.4 eta seed the models were trained with.
927 if (m_useXPitches) {
928 input.vectorOfPitchesY.assign(m_sizeY, design->etaPitch());
929 input.vectorOfPitchesX.assign(m_sizeX, design->phiPitch());
930 } else {
931 input.vectorOfPitchesY.assign(m_sizeY, 0.4);
932 }
933 rdosBegin = rdos.begin();
934 charge = chListRecreated.begin();
935 chargeEnd = chListRecreated.end();
936 tot = totListRecreated.begin();
937 ATH_MSG_VERBOSE(" Putting together the n. " << rdos.size() << " rdos into a matrix." );
938 Identifier pixidentif=pCluster.identify();
939 input.etaModule=(int)pixelID.eta_module(pixidentif);
940 input.ClusterPixLayer=(int)pixelID.layer_disk(pixidentif);
941 input.ClusterPixBarrelEC=(int)pixelID.barrel_ec(pixidentif);
942 for (;( charge != chargeEnd) and (rdosBegin!= rdosEnd); ++rdosBegin, ++charge, ++tot){
943 Identifier rId = *rdosBegin;
944 unsigned int absrow = pixelID.phi_index(rId)-rowWeightedPosition+centralIndexX;
945 unsigned int abscol = pixelID.eta_index(rId)-columnWeightedPosition+centralIndexY;
946 if (absrow > m_sizeX){
947 ATH_MSG_WARNING(" problem with index: " << absrow << " min: " << 0 << " max: " << m_sizeX);
948 return input;
949 }
950 if (abscol > m_sizeY){
951 ATH_MSG_WARNING(" problem with index: " << abscol << " min: " << 0 << " max: " << m_sizeY);
952 return input;
953 }
954 InDetDD::SiCellId cellId = element->cellIdFromIdentifier(*rdosBegin);
955 InDetDD::SiDiodesParameters diodeParameters = design->parameters(cellId);
956 double pitchY = diodeParameters.width().xEta();
957 double pitchX = diodeParameters.width().xPhi();
958 if (not m_useToT) {
959 input.matrixOfToT[absrow][abscol]=*charge;
960 } else {
961 input.matrixOfToT[absrow][abscol]=(double)(*tot);
962 // in case to RunI setup to make IBL studies
963 if(m_doRunI){
964 if (m_addIBL and (input.ClusterPixLayer==0) and (input.ClusterPixBarrelEC==0)){
965 input.matrixOfToT[absrow][abscol]*=3;
966 }
967 }else{
968 // for RunII IBL is always present
969 if ( (input.ClusterPixLayer==0) and (input.ClusterPixBarrelEC==0)){
970 input.matrixOfToT[absrow][abscol]*=3;
971 }
972 }
973
974 }
975 if (m_useXPitches) {
976 input.vectorOfPitchesY[abscol]=pitchY;
977 input.vectorOfPitchesX[absrow]=pitchX;
978 } else if (std::abs(pitchY-0.4)>1e-5){
979 // lwtnn: only override the 0.4 seed for long pixels
980 input.vectorOfPitchesY[abscol]=pitchY;
981 }
982 }//end iteration on rdos
983 ATH_MSG_VERBOSE(" eta module: " << input.etaModule );
984 ATH_MSG_VERBOSE(" Layer number: " << input.ClusterPixLayer << " Barrel / endcap: " << input.ClusterPixBarrelEC );
985 input.useTrackInfo=false;
986 const Amg::Vector2D& prdLocPos = pCluster.localPosition();
987 InDetDD::SiLocalPosition centroid(prdLocPos);
988 Amg::Vector3D globalPos = element->globalPosition(centroid);
989 Amg::Vector3D my_track = globalPos-beamSpotPosition;
990 const Amg::Vector3D &my_normal = element->normal();
991 const Amg::Vector3D &my_phiax = element->phiAxis();
992 const Amg::Vector3D &my_etaax = element->etaAxis();
993 float trkphicomp = my_track.dot(my_phiax);
994 float trketacomp = my_track.dot(my_etaax);
995 float trknormcomp = my_track.dot(my_normal);
996 double bowphi = std::atan2(trkphicomp,trknormcomp);
997 double boweta = std::atan2(trketacomp,trknormcomp);
998 tanl = m_pixelLorentzAngleTool->getTanLorentzAngle(element->identifyHash(), Gaudi::Hive::currentContext());
999 if(bowphi > M_PI_2) bowphi -= M_PI;
1000 if(bowphi < -M_PI_2) bowphi += M_PI;
1001 int readoutside = design->readoutSide();
1002 double angle = std::atan(std::tan(bowphi)-readoutside*tanl);
1003 input.phi=angle;
1004 ATH_MSG_VERBOSE(" Angle theta bef corr: " << boweta );
1005 if (boweta>M_PI_2) boweta-=M_PI;
1006 if (boweta<-M_PI_2) boweta+=M_PI;
1007 input.theta=boweta;
1008 ATH_MSG_VERBOSE(" Angle phi: " << angle << " theta: " << boweta );
1009 input.rowWeightedPosition=rowWeightedPosition;
1010 input.columnWeightedPosition=columnWeightedPosition;
1011 ATH_MSG_VERBOSE(" RowWeightedPosition: " << rowWeightedPosition << " ColWeightedPosition: " << columnWeightedPosition );
1012 return input;
1013 }//end create NNinput function
1014
1015 size_t
1017 return (m_sizeX * m_sizeY) + m_sizeY + (useTrackInfo ? 4 : 5);
1018 }
1019
1020 // ======================================================================
1021 // ONNX inference methods
1022 // ======================================================================
1023
1024 std::vector<double>
1026 const Eigen::VectorXd& input) const {
1027
1028 std::vector<double> result(3, 0.0);
1030 if (!onnxCollection.isValid()) {
1031 ATH_MSG_FATAL("Failed to get ONNX network collection with key " << m_readKeyONNX.key());
1032 return result;
1033 }
1034 Ort::Session& session = *onnxCollection->numberNetwork;
1035
1036 // Get expected input dimension from the model
1037 auto inputTypeInfo = session.GetInputTypeInfo(0);
1038 auto tensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
1039 const int64_t expectedDim = tensorInfo.GetShape()[1];
1040
1041 // Convert Eigen double vector to float
1042 if (static_cast<int64_t>(input.size()) != expectedDim) {
1043 ATH_MSG_FATAL("ONNX number network expects input dimension " << expectedDim
1044 << " but got " << input.size() << " — check model/configuration");
1045 return result;
1046 }
1047 std::vector<float> inputData(expectedDim);
1048 for (int i = 0; i < expectedDim; ++i) {
1049 inputData[i] = static_cast<float>(input[i]);
1050 }
1051
1052 // Create input tensor
1053 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
1054 std::vector<int64_t> inputShape = {1, expectedDim};
1055 Ort::Value inputTensor = Ort::Value::CreateTensor<float>(
1056 memInfo, inputData.data(), inputData.size(),
1057 inputShape.data(), inputShape.size());
1058 Ort::AllocatorWithDefaultOptions allocator;
1059 auto inputName = session.GetInputNameAllocated(0, allocator);
1060 auto outputName = session.GetOutputNameAllocated(0, allocator);
1061 const char* inputNames[] = {inputName.get()};
1062 const char* outputNames[] = {outputName.get()};
1063
1064 // Run inference
1065 auto outputTensors = session.Run(
1066 Ort::RunOptions{nullptr},
1067 inputNames, &inputTensor, 1,
1068 outputNames, 1);
1069
1070 // Extract output
1071 const float* outputData = outputTensors[0].GetTensorData<float>();
1072 double num0 = outputData[0];
1073 double num1 = outputData[1];
1074 double num2 = outputData[2];
1075
1076 // Normalize
1077 const double sum = num0 + num1 + num2;
1078 if (sum <= 0.0) {
1079 ATH_MSG_WARNING("ONNX number network output sum is non-positive: " << sum);
1080 return result;
1081 }
1082 const double inverseSum = 1.0 / sum;
1083 result[0] = num0 * inverseSum;
1084 result[1] = num1 * inverseSum;
1085 result[2] = num2 * inverseSum;
1086
1087 ATH_MSG_VERBOSE("ONNX Prob of n. particles (1): " << result[0]
1088 << " (2): " << result[1]
1089 << " (3): " << result[2]);
1090 return result;
1091 }
1092
1093 std::vector<Amg::Vector2D>
1095 const Eigen::VectorXd& input,
1096 NNinput& rawInput,
1097 const InDet::PixelCluster& pCluster,
1098 int numberSubClusters,
1099 std::vector<Amg::MatrixX>& errors) const {
1100
1101 std::vector<Amg::Vector2D> allPositions;
1102 if (numberSubClusters < 1 || numberSubClusters > static_cast<int>(m_maxSubClusters)) {
1103 return allPositions;
1104 }
1105
1107 if (!onnxCollection.isValid()) {
1108 ATH_MSG_FATAL("Failed to get ONNX network collection with key " << m_readKeyONNX.key());
1109 return allPositions;
1110 }
1111 Ort::Session* posNet = nullptr;
1112 if (numberSubClusters == 1) posNet = onnxCollection->positionNetwork1.get();
1113 else if (numberSubClusters == 2) posNet = onnxCollection->positionNetwork2.get();
1114 else if (numberSubClusters == 3) posNet = onnxCollection->positionNetwork3.get();
1115
1116 if (!posNet) {
1117 ATH_MSG_FATAL("ONNX position network for " << numberSubClusters
1118 << " sub-clusters not found in collection");
1119 return allPositions;
1120 }
1121
1122 Ort::Session& session = *posNet;
1123
1124 // Get expected input dimension from the model
1125 auto inputTypeInfo = session.GetInputTypeInfo(0);
1126 auto tensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
1127 const int64_t expectedDim = tensorInfo.GetShape()[1];
1128
1129 // Convert input to float
1130 if (static_cast<int64_t>(input.size()) != expectedDim) {
1131 ATH_MSG_FATAL("ONNX position network (" << numberSubClusters
1132 << " sub-clusters) expects input dimension " << expectedDim
1133 << " but got " << input.size() << " — check model/configuration");
1134 return allPositions;
1135 }
1136 std::vector<float> inputData(expectedDim);
1137 for (int i = 0; i < expectedDim; ++i) {
1138 inputData[i] = static_cast<float>(input[i]);
1139 }
1140
1141 // Create input tensor
1142 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
1143 std::vector<int64_t> inputShape = {1, expectedDim};
1144 Ort::Value inputTensor = Ort::Value::CreateTensor<float>(
1145 memInfo, inputData.data(), inputData.size(),
1146 inputShape.data(), inputShape.size());
1147 Ort::AllocatorWithDefaultOptions allocator;
1148 auto inputName = session.GetInputNameAllocated(0, allocator);
1149 auto outputName = session.GetOutputNameAllocated(0, allocator);
1150 const char* inputNames[] = {inputName.get()};
1151 const char* outputNames[] = {outputName.get()};
1152
1153 // Run inference
1154 auto outputTensors = session.Run(
1155 Ort::RunOptions{nullptr},
1156 inputNames, &inputTensor, 1,
1157 outputNames, 1);
1158
1159 // Extract output: expect [1, 5*numberSubClusters]
1160 // Format per sub-cluster: [alpha, mean_x, mean_y, prec_x, prec_y]
1161 const float* outputData = outputTensors[0].GetTensorData<float>();
1162
1163 std::vector<double> positionValues;
1164 positionValues.reserve(numberSubClusters * 2);
1165
1166 for (int iSub = 0; iSub < numberSubClusters; ++iSub) {
1167 const int offset = iSub * 5;
1168 // outputData[offset+0] = alpha (unused)
1169 const double mean_x = outputData[offset + 1];
1170 const double mean_y = outputData[offset + 2];
1171 const double prec_x = outputData[offset + 3];
1172 const double prec_y = outputData[offset + 4];
1173
1174 positionValues.push_back(mean_x);
1175 positionValues.push_back(mean_y);
1176
1177 // Convert precision to RMS and build error matrix
1178 if (prec_x <= 0 || prec_y <= 0) {
1179 ATH_MSG_WARNING("ONNX position network returned non-positive precision for sub-cluster "
1180 << iSub << " (prec_x=" << prec_x << ", prec_y=" << prec_y
1181 << "); using fallback RMS of 0.01");
1182 }
1183 const float rawRmsX = (prec_x > 0) ? std::sqrt(1.0f / prec_x) : 0.01f;
1184 const float rawRmsY = (prec_y > 0) ? std::sqrt(1.0f / prec_y) : 0.01f;
1185 const double rmsX = correctedRMS(rawRmsX, rawInput.vectorOfPitchesX, m_sizeX);
1186 const double rmsY = correctedRMS(rawRmsY, rawInput.vectorOfPitchesY, m_sizeY);
1187
1188 Amg::MatrixX erm(2, 2);
1189 erm.setZero();
1190 erm(0, 0) = rmsX * rmsX;
1191 erm(1, 1) = rmsY * rmsY;
1192 errors.push_back(std::move(erm));
1193 }
1194
1195 // Convert raw position outputs to detector coordinates
1196 allPositions = getPositionsFromOutput(positionValues, rawInput, pCluster);
1197 return allPositions;
1198 }
1199
1200}//end InDet namespace
#define M_PI
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_FATAL(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
double charge(const T &p)
Definition AtlasPID.h:1003
This file defines the class for a collection of AttributeLists where each one is associated with a ch...
static Double_t a
double norm_rawToT(const double input)
double norm_pitch(const double input, bool addIBL=false)
double errorHalfIntervalY(const int nParticles)
double norm_layerNumber(const double input)
double norm_thetaBS(const double input)
double norm_layerType(const double input)
double norm_ToT(const double input)
double back_posX(const double input, const bool recenter=false)
double back_posY(const double input)
double norm_phi(const double input)
double norm_phiBS(const double input)
double norm_theta(const double input)
double norm_etaModule(const double input)
double errorHalfIntervalX(const int nParticles)
This is an Identifier helper class for the Pixel subdetector.
size_t size() const
Number of registered mappings.
double angle(const GeoTrf::Vector2D &a, const GeoTrf::Vector2D &b)
static const Attributes_t empty
AthAlgTool(const std::string &type, const std::string &name, const IInterface *parent)
Constructor with parameters:
This class provides an interface to generate or decode an identifier for the upper levels of the dete...
virtual HelperType helper() const
Type of helper, defaulted to 'Unimplemented'.
This is a "hash" representation of an Identifier.
int readoutSide() const
ReadoutSide.
static constexpr std::array< PixelDiodeTree::CellIndexType, 2 > makeCellIndex(T local_x_idx, T local_y_idx)
Create a 2D cell index from the indices in local-x (phi, row) and local-y (eta, column) direction.
Class used to describe the design of a module (diode segmentation and readout scheme).
virtual SiDiodesParameters parameters(const SiCellId &cellId) const
readout or diode id -> position, size
virtual int numberOfConnectedCells(const SiReadoutCellId &readoutId) const
readout id -> id of connected diodes
PixelReadoutTechnology getReadoutTechnology() const
PixelDiodeTree::DiodeProxy diodeProxyFromIdx(const std::array< PixelDiodeTree::IndexType, 2 > &idx) const
SiLocalPosition positionFromColumnRow(const int column, const int row) const
Given row and column index of a diode, return position of diode center ALTERNATIVE/PREFERED way is to...
virtual SiReadoutCellId readoutIdOfCell(const SiCellId &cellId) const
diode id -> readout id
static InDetDD::PixelDiodeType getDiodeType(const PixelDiodeTree::DiodeProxy &diode_proxy)
virtual SiCellId cellIdOfPosition(const SiLocalPosition &localPos) const
position -> id
virtual double width() const
Method to calculate average width of a module.
virtual double etaPitch() const
Pitch in eta direction.
static unsigned int getFE(const PixelDiodeTree::DiodeProxy &diode_proxy)
virtual double phiPitch() const
Pitch in phi direction.
Identifier for the strip or pixel cell.
Definition SiCellId.h:29
int phiIndex() const
Get phi index. Equivalent to strip().
Definition SiCellId.h:122
bool isValid() const
Test if its in a valid state.
Definition SiCellId.h:136
int etaIndex() const
Get eta index.
Definition SiCellId.h:114
Class to hold geometrical description of a silicon detector element.
virtual SiCellId cellIdFromIdentifier(const Identifier &identifier) const override final
SiCellId from Identifier.
virtual const SiDetectorDesign & design() const override final
access to the local description (inline):
Class to handle the position of the centre and the width of a diode or a cluster of diodes Version 1....
const SiLocalPosition & width() const
width of the diodes:
Class to represent a position in the natural frame of a silicon sensor, for Pixel and SCT For Pixel: ...
double xPhi() const
position along phi direction:
double xEta() const
position along eta direction:
virtual const Amg::Vector3D & normal() const override final
Get reconstruction local normal axes in global frame.
virtual IdentifierHash identifyHash() const override final
identifier hash (inline)
HepGeom::Point3D< double > globalPosition(const HepGeom::Point3D< double > &localPos) const
transform a reconstruction local position into a global position (inline):
const AtlasDetectorID * getIdHelper() const
Returns the id helper (inline).
std::vector< double > assembleInputRunII(NNinput &input) const
void addTrackInfoToInput(NNinput &input, const Trk::Surface &pixelSurface, const Trk::TrackParameters &trackParsAtSurface, const double tanl) const
Gaudi::Property< unsigned int > m_maxSubClusters
SG::ReadCondHandleKey< PixelChargeCalibCondData > m_chargeDataKey
std::vector< double > estimateNumberOfParticlesLWTNN(NnClusterizationFactory::InputVector &input) const
double correctedRMS(double posPixels, const std::vector< float > &pitches, unsigned int size) const
std::vector< Amg::Vector2D > estimatePositionsONNX(const Eigen::VectorXd &input, NNinput &rawInput, const InDet::PixelCluster &pCluster, int numberSubClusters, std::vector< Amg::MatrixX > &errors) const
Gaudi::Property< unsigned int > m_sizeX
ReturnType(::TTrainedNetwork::* m_calculateOutput)(const InputType &input) const
NNinput createInput(const InDet::PixelCluster &pCluster, Amg::Vector3D &beamSpotPosition, double &tanl) const
virtual StatusCode initialize() override
Gaudi::Property< double > m_correctLorShiftBarrelWithoutTracks
Gaudi::Property< std::size_t > m_outputNodesPos1
Gaudi::Property< std::vector< std::size_t > > m_outputNodesPos2
ToolHandle< ISiLorentzAngleTool > m_pixelLorentzAngleTool
Gaudi::Property< std::vector< std::size_t > > m_outputNodesPos3
std::vector< Amg::Vector2D > estimatePositionsLWTNN(NnClusterizationFactory::InputVector &input, NNinput &rawInput, const InDet::PixelCluster &pCluster, int numberSubClusters, std::vector< Amg::MatrixX > &errors) const
SG::ReadCondHandleKey< OnnxNNCollection > m_readKeyONNX
std::vector< double > assembleInputRunI(NNinput &input) const
SG::ReadCondHandleKey< LWTNNCollection > m_readKeyJSON
std::vector< Amg::Vector2D > estimatePositions(const InDet::PixelCluster &pCluster, Amg::Vector3D &beamSpotPosition, std::vector< Amg::MatrixX > &errors, int numberSubClusters) const
Gaudi::Property< std::vector< std::string > > m_nnOrder
Gaudi::Property< double > m_correctLorShiftBarrelWithTracks
Gaudi::Property< bool > m_useTTrainedNetworks
std::vector< double > estimateNumberOfParticlesTTN(const TTrainedNetworkCollection &nn_collection, const std::vector< double > &inputData) const
static constexpr std::array< unsigned int, kNNetworkTypes > m_nParticleGroup
SG::ReadCondHandleKey< TTrainedNetworkCollection > m_readKeyWithoutTrack
std::vector< double >(InDet::NnClusterizationFactory::* m_assembleInput)(NNinput &input) const
std::vector< Eigen::VectorXd > InputVector
NnClusterizationFactory(const std::string &name, const std::string &n, const IInterface *p)
std::vector< Amg::Vector2D > estimatePositionsTTN(const TTrainedNetworkCollection &nn_collection, const std::vector< double > &inputData, const NNinput &input, const InDet::PixelCluster &pCluster, int numberSubClusters, std::vector< Amg::MatrixX > &errors) const
static const std::array< std::regex, kNNetworkTypes > m_nnNames
Gaudi::Property< bool > m_useRecenteringNNWithouTracks
static constexpr std::array< std::string_view, kNNetworkTypes > s_nnTypeNames
InputVector eigenInput(NNinput &input) const
void getErrorMatrixFromOutput(std::vector< double > &outputX, std::vector< double > &outputY, std::vector< Amg::MatrixX > &errorMatrix, int nParticles) const
Gaudi::Property< bool > m_useRecenteringNNWithTracks
std::vector< std::vector< unsigned int > > m_NNId
std::vector< Amg::Vector2D > getPositionsFromOutput(std::vector< double > &output, const NNinput &input, const InDet::PixelCluster &pCluster) const
size_t calculateVectorDimension(const bool useTrackInfo) const
Gaudi::Property< unsigned int > m_sizeY
SG::ReadCondHandleKey< TTrainedNetworkCollection > m_readKeyWithTrack
std::vector< double > estimateNumberOfParticlesONNX(const Eigen::VectorXd &input) const
std::vector< double > estimateNumberOfParticles(const InDet::PixelCluster &pCluster, Amg::Vector3D &beamSpotPosition) const
virtual const InDetDD::SiDetectorElement * detectorElement() const override final
return the detector element corresponding to this PRD The pointer will be zero if the det el is not d...
float getCharge(InDetDD::PixelDiodeType type, unsigned int moduleHash, unsigned int FE, float ToT) const
This is an Identifier helper class for the Pixel subdetector.
Definition PixelID.h:69
int eta_index(const Identifier &id) const
Definition PixelID.h:640
int layer_disk(const Identifier &id) const
Definition PixelID.h:602
Identifier wafer_id(int barrel_ec, int layer_disk, int phi_module, int eta_module) const
For a single crystal.
Definition PixelID.h:355
int barrel_ec(const Identifier &id) const
Values of different levels (failure returns 0).
Definition PixelID.h:595
IdentifierHash wafer_hash(Identifier wafer_id) const
wafer hash from id
Definition PixelID.h:378
int eta_module(const Identifier &id) const
Definition PixelID.h:627
int phi_index(const Identifier &id) const
Definition PixelID.h:634
std::vector< Double_t > calculateOutputValues(std::vector< Double_t > &input) const
const Amg::Vector3D & momentum() const
Access method for the momentum.
const Amg::Vector2D & localPosition() const
return the local position reference
Identifier identify() const
return the identifier
const std::vector< Identifier > & rdoList() const
return the List of rdo identifiers (pointers)
Abstract Base Class for tracking surfaces.
Definition Surface.h:79
const Amg::Transform3D & transform() const
Returns HepGeom::Transform3D by reference.
STL class.
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic > MatrixX
Dynamic Matrix - dynamic allocation.
Eigen::Matrix< double, 2, 1 > Vector2D
Eigen::Matrix< double, 3, 1 > Vector3D
Primary Vertex Finder.
@ locY
local cartesian
Definition ParamDefs.h:38
@ locX
Definition ParamDefs.h:37
ParametersBase< TrackParametersDim, Charged > TrackParameters
Helper class to access parameters of a diode.
std::vector< float > vectorOfPitchesX
dimensions of pixels in mm along Y
std::vector< float > vectorOfPitchesY
2D array of charges or ToTs (depending on filling tool configuration)