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;
479 inputDataNew.reserve( inputDataNew.size() + nPositions);
480 inputDataNew.insert(inputDataNew.end(), inputData.begin(), inputData.end());
481 inputDataNew.insert(inputDataNew.end(), position1P.begin(), position1P.begin() + nPositions);
482
483 // get error network id for the given cluster multiplicity then
484 // dereference unique_ptr<TTrainedNetwork> then call calculateOutput :
485 const auto xNetworkIndex = m_NNId[kErrorXNN-1].at(subClusterIndex);
486 const auto yNetworkIndex = m_NNId[kErrorYNN-1].at(subClusterIndex);
487 if ((not (xNetworkIndex < endNnIdx)) or (not (yNetworkIndex < endNnIdx))){
488 ATH_MSG_FATAL("estimatePositionsTTN: A requested collection index, "<< xNetworkIndex << " or "<< yNetworkIndex << "is out of range.");
489 return allPositions;
490 }
491 auto *pxNetwork = nn_collection.at(xNetworkIndex).get();
492 auto *pyNetwork = nn_collection.at(yNetworkIndex).get();
493 //call the selected member function of the TTrainedNetwork
494 std::vector<double> errors1PX = (*pxNetwork.*m_calculateOutput)(inputDataNew);
495 std::vector<double> errors1PY = (*pyNetwork.*m_calculateOutput)(inputDataNew);
496 //
497 std::vector<Amg::MatrixX> errorMatrices1;
498 getErrorMatrixFromOutput(errors1PX,errors1PY,errorMatrices1,numberSubClusters);
499 allPositions.reserve( allPositions.size() + myPosition1.size());
500 errors.reserve( errors.size() + myPosition1.size());
501 for (unsigned int i=0;i<myPosition1.size();i++){
502 allPositions.push_back(myPosition1[i]);
503 errors.push_back(errorMatrices1[i]);
504 }
505 }
506 return allPositions;
507 }
508
509
510 std::vector<Amg::Vector2D>
512 NNinput& rawInput,
513 const InDet::PixelCluster& pCluster,
514 int numberSubClusters,
515 std::vector<Amg::MatrixX> & errors) const {
517 if (not lwtnn_collection.isValid()) {
518 ATH_MSG_FATAL( "Failed to get LWTNN network collection with key " << m_readKeyJSON.key() );
519 return {};
520 }
521 if (lwtnn_collection->empty()){
522 ATH_MSG_FATAL( "estimatePositionsLWTNN: LWTNN network collection with key " << m_readKeyJSON.key()<<" is empty." );
523 return {};
524 }
525 // Need to evaluate the correct network once per cluster we're interested in.
526 // Save the output
527 std::vector<double> positionValues{};
528 std::vector<Amg::MatrixX> errorMatrices;
529 errorMatrices.reserve(numberSubClusters);
530 positionValues.reserve(numberSubClusters * 2);
531 std::size_t outputNode(0);
532 for (int cluster = 1; cluster < numberSubClusters+1; cluster++) {
533 // Check that the network is defined.
534 // If not, we are outside an IOV and should fail
535 const auto pNetwork = lwtnn_collection->find(numberSubClusters);
536 const bool validGraph = (pNetwork != lwtnn_collection->end()) and (pNetwork->second != nullptr);
537 if (not validGraph) {
538 std::string infoMsg ="Acceptable numbers of subclusters for the lwtnn collection:\n ";
539 for (const auto & pair: **lwtnn_collection){
540 infoMsg += std::to_string(pair.first) + "\n ";
541 }
542 infoMsg += "\nNumber of subclusters requested : "+ std::to_string(numberSubClusters);
543 ATH_MSG_DEBUG(infoMsg);
544 ATH_MSG_FATAL( "estimatePositionsLWTNN: No lwtnn network found for the number of clusters.\n"
545 <<" If you are outside the valid range for an lwtnn-based configuration, please run with useNNTTrainedNetworks instead.\n Key = "
546 << m_readKeyJSON.key() );
547 return {};
548 }
549 if(numberSubClusters==1) {
550 outputNode = m_outputNodesPos1;
551 } else if(numberSubClusters==2) {
552 outputNode = m_outputNodesPos2[cluster-1];
553 } else if(numberSubClusters==3) {
554 outputNode = m_outputNodesPos3[cluster-1];
555 } else {
556 ATH_MSG_FATAL( "Cannot evaluate LWTNN networks with " << numberSubClusters << " numberSubClusters" );
557 return {};
558 }
559
560 // Order of output matches order in JSON config in "outputs"
561 // "alpha", "mean_x", "mean_y", "prec_x", "prec_y"
562 // Assume here that 1 particle network is in position 1, 2 at 2, and 3 at 3.
563 Eigen::VectorXd position = lwtnn_collection->at(numberSubClusters)->compute(input, {}, outputNode);
564 ATH_MSG_DEBUG("Testing for numberSubClusters " << numberSubClusters << " and cluster " << cluster);
565 for (int i=0; i<position.rows(); i++) {
566 ATH_MSG_DEBUG(" position " << position[i]);
567 }
568 positionValues.push_back(position[1]); //mean_x
569 positionValues.push_back(position[2]); //mean_y
570 // Fill errors.
571 // Values returned by NN are inverse of variance, and we want variances.
572 const float rawRmsX = std::sqrt(1.0/position[3]); //prec_x
573 const float rawRmsY = std::sqrt(1.0/position[4]); //prec_y
574 // Convert to real space units: x uses the nominal pixel pitch, y
575 // integrates the actual pitches. (estimatePositionsONNX uses correctedRMS
576 // for both directions.)
577 const double rmsX = rawRmsX * legacyPhiPitch;
578 const double rmsY = correctedRMS(rawRmsY, rawInput.vectorOfPitchesY, m_sizeY);
579 ATH_MSG_DEBUG(" Estimated RMS errors (1) x: " << rmsX << ", y: " << rmsY);
580 // Fill matrix
581 Amg::MatrixX erm(2,2);
582 erm.setZero();
583 erm(0,0)=rmsX*rmsX;
584 erm(1,1)=rmsY*rmsY;
585 errorMatrices.push_back(std::move(erm));
586 }
587 std::vector<Amg::Vector2D> myPositions = getPositionsFromOutput(positionValues,rawInput,pCluster);
588 ATH_MSG_DEBUG(" Estimated myPositions (1) x: " << myPositions[0][Trk::locX] << " y: " << myPositions[0][Trk::locY]);
589 errors=std::move(errorMatrices);
590 return myPositions;
591 }
592
593 double
595 const std::vector<float>& pitches,
596 unsigned int size) const{
597 // Convert a pixel-unit RMS to a distance by integrating the actual pitches,
598 // so non-uniform pitch (ITk long/end pixels, 25 um modules) is handled.
599 // size is m_sizeX in phi (x), m_sizeY in eta (y).
600 double p = posPixels + (size - 1) * 0.5;
601 double p_pos = unsetPos;
602 double p_center = unsetPos;
603 double p_actual = 0;
604 for (unsigned int i = 0; i < size; i++) {
605 if (p >= i and p <= (i + 1)) p_pos = p_actual + (p - i + 0.5) * pitches.at(i);
606 if (i == (size - 1) / 2) p_center = p_actual + 0.5 * pitches.at(i);
607 p_actual += pitches.at(i);
608 }
609 return std::abs(p_pos - p_center);
610 }
611
612 void
614 std::vector<double>& outputY,
615 std::vector<Amg::MatrixX>& errorMatrix,
616 int nParticles) const{
617 int sizeOutputX=outputX.size()/nParticles;
618 int sizeOutputY=outputY.size()/nParticles;
619 double minimumX=-errorHalfIntervalX(nParticles);
620 double maximumX=errorHalfIntervalX(nParticles);
621 double minimumY=-errorHalfIntervalY(nParticles);
622 double maximumY=errorHalfIntervalY(nParticles);
623 //X=0...sizeOutput-1
624 //Y=minimum+(maximum-minimum)/sizeOutput*(X+1./2.)
625 errorMatrix.reserve( errorMatrix.size() + nParticles);
626 for (int i=0;i<nParticles;i++){
627 double sumValuesX=0;
628 for (int u=0;u<sizeOutputX;u++){
629 sumValuesX+=outputX[i*sizeOutputX+u];
630 }
631 double sumValuesY=0;
632 for (int u=0;u<sizeOutputY;u++){
633 sumValuesY+=outputY[i*sizeOutputY+u];
634 }
635 ATH_MSG_VERBOSE(" minimumX: " << minimumX << " maximumX: " << maximumX << " sizeOutputX " << sizeOutputX);
636 ATH_MSG_VERBOSE(" minimumY: " << minimumY << " maximumY: " << maximumY << " sizeOutputY " << sizeOutputY);
637 double RMSx=0;
638 for (int u=0;u<sizeOutputX;u++){
639 RMSx+=outputX[i*sizeOutputX+u]/sumValuesX*std::pow(minimumX+(maximumX-minimumX)/(double)(sizeOutputX-2)*(u-1./2.),2);
640 }
641 RMSx=std::sqrt(RMSx);//computed error!
642 ATH_MSG_VERBOSE(" first Iter RMSx: " << RMSx);
643 double intervalErrorX=3*RMSx;
644 //now recompute between -3*RMSx and +3*RMSx
645 int minBinX=(int)(1+(-intervalErrorX-minimumX)/(maximumX-minimumX)*(double)(sizeOutputX-2));
646 int maxBinX=(int)(1+(intervalErrorX-minimumX)/(maximumX-minimumX)*(double)(sizeOutputX-2));
647 if (maxBinX>sizeOutputX-1) maxBinX=sizeOutputX-1;
648 if (minBinX<0) minBinX=0;
649 ATH_MSG_VERBOSE(" minBinX: " << minBinX << " maxBinX: " << maxBinX );
650 RMSx=0;
651 for (int u=minBinX;u<maxBinX+1;u++){
652 RMSx+=outputX[i*sizeOutputX+u]/sumValuesX*std::pow(minimumX+(maximumX-minimumX)/(double)(sizeOutputX-2)*(u-1./2.),2);
653 }
654 RMSx=std::sqrt(RMSx);//computed error!
655 double RMSy=0;
656 for (int u=0;u<sizeOutputY;u++){
657 RMSy+=outputY[i*sizeOutputY+u]/sumValuesY*std::pow(minimumY+(maximumY-minimumY)/(double)(sizeOutputY-2)*(u-1./2.),2);
658 }
659 RMSy=std::sqrt(RMSy);//computed error!
660 ATH_MSG_VERBOSE("first Iter RMSy: " << RMSy );
661 double intervalErrorY=3*RMSy;
662 //now recompute between -3*RMSy and +3*RMSy
663 int minBinY=(int)(1+(-intervalErrorY-minimumY)/(maximumY-minimumY)*(double)(sizeOutputY-2));
664 int maxBinY=(int)(1+(intervalErrorY-minimumY)/(maximumY-minimumY)*(double)(sizeOutputY-2));
665 if (maxBinY>sizeOutputY-1) maxBinY=sizeOutputY-1;
666 if (minBinY<0) minBinY=0;
667 ATH_MSG_VERBOSE("minBinY: " << minBinY << " maxBinY: " << maxBinY );
668 RMSy=0;
669 for (int u=minBinY;u<maxBinY+1;u++){
670 RMSy+=outputY[i*sizeOutputY+u]/sumValuesY*std::pow(minimumY+(maximumY-minimumY)/(double)(sizeOutputY-2)*(u-1./2.),2);
671 }
672 RMSy=std::sqrt(RMSy);//computed error!
673 ATH_MSG_VERBOSE("Computed error, sigma(X) " << RMSx << " sigma(Y) " << RMSy );
674 Amg::MatrixX erm(2,2);
675 erm.setZero();
676 erm(0,0)=RMSx*RMSx;
677 erm(1,1)=RMSy*RMSy;
678 errorMatrix.push_back(std::move(erm));
679 }//end nParticles
680 }//getErrorMatrixFromOutput
681
682
683 std::vector<Amg::Vector2D>
685 const NNinput & input,
686 const InDet::PixelCluster& pCluster) const{
687 ATH_MSG_VERBOSE(" Translating output back into a position " );
688 const InDetDD::SiDetectorElement* element=pCluster.detectorElement();//DEFINE
689 const InDetDD::PixelModuleDesign* design
690 (dynamic_cast<const InDetDD::PixelModuleDesign*>(&element->design()));
691 if (not design){
692 ATH_MSG_ERROR("Dynamic cast failed at line "<<__LINE__<<" of NnClusterizationFactory.cxx.");
693 return {};
694 }
695 int numParticles=output.size()/2;
696 int columnWeightedPosition=input.columnWeightedPosition;
697 int rowWeightedPosition=input.rowWeightedPosition;
698 ATH_MSG_VERBOSE(" REF POS columnWeightedPos: " << columnWeightedPosition << " rowWeightedPos: " << rowWeightedPosition );
699 bool applyRecentering=false;
700 if (m_useRecenteringNNWithouTracks and (not input.useTrackInfo)){
701 applyRecentering=true;
702 }
703 if (m_useRecenteringNNWithTracks and input.useTrackInfo){
704 applyRecentering=true;
705 }
706 std::vector<Amg::Vector2D> positions;
707 for (int u=0;u<numParticles;u++){
708 double posXid{};
709 double posYid{};
710 if(m_doRunI){
711 posXid=back_posX(output[2*u],applyRecentering)+rowWeightedPosition;
712 posYid=back_posY(output[2*u+1])+columnWeightedPosition;
713 }else{
714 posXid=output[2*u]+rowWeightedPosition;
715 posYid=output[2*u+1]+columnWeightedPosition;
716 }
717 ATH_MSG_VERBOSE(" N. particle: " << u << " idx posX " << posXid << " posY " << posYid );
718 //ATLASRECTS-7155 : Pixel Charge Calibration needs investigating
719 const auto & [posXid_int, coercedX]=coerceToIntRange(posXid+0.5);
720 const auto & [posYid_int, coercedY]=coerceToIntRange(posYid+0.5);
721 if (coercedX or coercedY){
722 ATH_MSG_WARNING("X or Y position value has been limited in range; original values are (" << posXid<<", "<<posYid<<")");
723 //we cannot skip these values, it seems client code relies on the size of input vector and output vector being the same
724 }
725 ATH_MSG_VERBOSE(" N. particle: " << u << " TO INTEGER idx posX " << posXid_int << " posY " << posYid_int );
726 InDetDD::SiLocalPosition siLocalPositionDiscrete(design->positionFromColumnRow(posYid_int,posXid_int));
727 InDetDD::SiCellId cellIdOfPositionDiscrete=design->cellIdOfPosition(siLocalPositionDiscrete);
728 if ( not cellIdOfPositionDiscrete.isValid()){
729 ATH_MSG_WARNING(" Cell is outside validity region with index Y: " << posYid_int << " and index X: " << posXid_int << ". Not foreseen... " );
730 }
731 InDetDD::SiDiodesParameters diodeParameters = design->parameters(cellIdOfPositionDiscrete);
732 double pitchY = diodeParameters.width().xEta();
733 double pitchX = diodeParameters.width().xPhi();
734 ATH_MSG_VERBOSE(" Translated weighted position : " << siLocalPositionDiscrete.xPhi()
735 << " Translated weighted position : " << siLocalPositionDiscrete.xEta() );
736 //FOR TEST
737 InDetDD::SiLocalPosition siLocalPositionDiscreteOneRowMoreOneColumnMore(design->positionFromColumnRow(posYid_int+1,posXid_int+1));
738 ATH_MSG_VERBOSE(" Translated weighted position +1col +1row phi: " << siLocalPositionDiscreteOneRowMoreOneColumnMore.xPhi()
739 << " Translated weighted position +1col +1row eta: " << siLocalPositionDiscreteOneRowMoreOneColumnMore.xEta() );
740 ATH_MSG_VERBOSE("PitchY: " << pitchY << " pitchX " << pitchX );
741 InDetDD::SiLocalPosition siLocalPositionAdd(pitchY*(posYid-(double)posYid_int),
742 pitchX*(posXid-(double)posXid_int));
743 double lorentzShift=m_pixelLorentzAngleTool->getLorentzShift(element->identifyHash(), Gaudi::Hive::currentContext());
744 if (input.ClusterPixBarrelEC == 0){
745 if (not input.useTrackInfo){
747 } else {
749 }
750 }
751
753 siLocalPosition(siLocalPositionDiscrete.xEta()+pitchY*(posYid-(double)posYid_int),
754 siLocalPositionDiscrete.xPhi()+pitchX*(posXid-(double)posXid_int)+lorentzShift);
755 ATH_MSG_VERBOSE(" Translated final position phi: " << siLocalPosition.xPhi() << " eta: " << siLocalPosition.xEta() );
756 const auto halfWidth{design->width()*0.5};
757 if (siLocalPositionDiscrete.xPhi() > halfWidth){
758 siLocalPosition=InDetDD::SiLocalPosition(siLocalPositionDiscrete.xEta()+pitchY*(posYid-(double)posYid_int),
759 halfWidth-1e-6);
760 ATH_MSG_WARNING(" Corrected out of boundary cluster from x(phi): " << siLocalPositionDiscrete.xPhi()+pitchX*(posXid-(double)posXid_int)
761 << " to: " << halfWidth-1e-6);
762 } else if (siLocalPositionDiscrete.xPhi() < -halfWidth) {
763 siLocalPosition=InDetDD::SiLocalPosition(siLocalPositionDiscrete.xEta()+pitchY*(posYid-(double)posYid_int),
764 -halfWidth+1e-6);
765 ATH_MSG_WARNING(" Corrected out of boundary cluster from x(phi): " << siLocalPositionDiscrete.xPhi()+pitchX*(posXid-(double)posXid_int)
766 << " to: " << -halfWidth+1e-6);
767 }
768 positions.emplace_back(siLocalPosition);
769 }//iterate over all particles
770 return positions;
771 }
772
773
774 void
776 const Trk::Surface& pixelSurface, // pixelSurface = pcot->associatedSurface();
777 const Trk::TrackParameters& trackParsAtSurface,
778 const double tanl) const {
779 input.useTrackInfo=true;
780 Amg::Vector3D particleDir = trackParsAtSurface.momentum().unit();
781 Amg::Vector3D localIntersection = pixelSurface.transform().inverse().linear() * particleDir;
782 localIntersection *= 0.250/cos(localIntersection.theta());
783 float trackDeltaX = (float)localIntersection.x();
784 float trackDeltaY = (float)localIntersection.y();
785 input.theta=std::atan2(trackDeltaY,0.250);
786 input.phi=std::atan2(trackDeltaX,0.250);
787 ATH_MSG_VERBOSE("Angle phi bef Lorentz corr: " << input.phi );
788 input.phi=std::atan(std::tan(input.phi)-tanl);
789 ATH_MSG_VERBOSE(" From track: angle phi: " << input.phi << " theta: " << input.theta );
790 }
791
792
793 NNinput
795 Amg::Vector3D & beamSpotPosition,
796 double & tanl) const{
797 NNinput input;
798 ATH_MSG_VERBOSE(" Starting creating input from cluster " );
799 const InDetDD::SiDetectorElement* element=pCluster.detectorElement();
800 if (not element) {
801 ATH_MSG_ERROR("Could not get detector element");
802 return input;
803 }
804 const AtlasDetectorID* aid = element->getIdHelper();
805 if (not aid){
806 ATH_MSG_ERROR("Could not get ATLASDetectorID");
807 return input;
808 }
809
811 ATH_MSG_ERROR("Could not get PixelID pointer");
812 return input;
813 }
814 const PixelID* pixelIDp=static_cast<const PixelID*>(aid);
815 const PixelID& pixelID = *pixelIDp;
816 const InDetDD::PixelModuleDesign* design
817 (dynamic_cast<const InDetDD::PixelModuleDesign*>(&element->design()));
818 if (not design){
819 ATH_MSG_ERROR("Dynamic cast failed at line "<<__LINE__<<" of NnClusterizationFactory.cxx.");
820 return input;
821 }
823 const PixelChargeCalibCondData *calibData = *calibDataHandle;
824 const std::vector<Identifier>& rdos = pCluster.rdoList();
825 const size_t rdoSize = rdos.size();
826 ATH_MSG_VERBOSE(" Number of RDOs: " << rdoSize );
827 const std::vector<float>& chList = pCluster.chargeList();
828 const std::vector<int>& totList = pCluster.totList();
829 std::vector<float> chListRecreated{};
830 chListRecreated.reserve(rdoSize);
831 ATH_MSG_VERBOSE(" Number of charges: " << chList.size() );
832 std::vector<int>::const_iterator tot = totList.begin();
833 std::vector<Identifier>::const_iterator rdosBegin = rdos.begin();
834 std::vector<Identifier>::const_iterator rdosEnd = rdos.end();
835 std::vector<int> totListRecreated{};
836 totListRecreated.reserve(rdoSize);
837 std::vector<int>::const_iterator totRecreated = totListRecreated.begin();
838 // Recreate both charge list and ToT list to correct for the IBL ToT overflow (and later for small hits):
839 ATH_MSG_VERBOSE("Charge list is not filled ... re-creating it.");
840 IdentifierHash moduleHash = element->identifyHash(); // wafer hash
841
842 for ( ; rdosBegin!= rdosEnd and tot != totList.end(); ++tot, ++rdosBegin, ++totRecreated ){
843 // recreate the charge: should be a method of the calibSvc
844 int tot0 = *tot;
845 Identifier pixid = *rdosBegin;
846 assert( element->identifyHash() == pixelID.wafer_hash(pixelID.wafer_id(pixid)));
847
848 std::array<InDetDD::PixelDiodeTree::CellIndexType,2> diode_idx
850 pixelID.eta_index(pixid));
851 InDetDD::PixelDiodeTree::DiodeProxy si_param ( design->diodeProxyFromIdx(diode_idx));
852 std::uint32_t feValue = design->getFE(si_param);
853 auto diode_type = design->getDiodeType(si_param);
855 && design->numberOfConnectedCells( design->readoutIdOfCell(InDetDD::SiCellId(diode_idx[0],diode_idx[1])))>1) {
857 }
858
859 float charge = calibData->getCharge(diode_type, moduleHash, feValue, tot0);
860 chListRecreated.push_back(charge);
861 totListRecreated.push_back(tot0);
862 }
863 // reset the rdo iterator
864 rdosBegin = rdos.begin();
865 rdosEnd = rdos.end();
866 // and the tot iterator
867 tot = totList.begin();
868 totRecreated = totListRecreated.begin();
869 // Always use recreated charge and ToT lists:
870 std::vector<float>::const_iterator charge = chListRecreated.begin();
871 std::vector<float>::const_iterator chargeEnd = chListRecreated.end();
872 tot = totListRecreated.begin();
873 std::vector<int>::const_iterator totEnd = totListRecreated.end();
874 InDetDD::SiLocalPosition sumOfWeightedPositions(0,0,0);
875 double sumOfTot=0;
876 int rowMin = 999;
877 int rowMax = 0;
878 int colMin = 999;
879 int colMax = 0;
880 for (; (rdosBegin!= rdosEnd) and (charge != chargeEnd) and (tot != totEnd); ++rdosBegin, ++charge, ++tot){
881 Identifier rId = *rdosBegin;
882 int row = pixelID.phi_index(rId);
883 int col = pixelID.eta_index(rId);
884 InDetDD::SiLocalPosition siLocalPosition (design->positionFromColumnRow(col,row));
885 if (not m_useToT){
886 sumOfWeightedPositions += (*charge)*siLocalPosition;
887 sumOfTot += (*charge);
888 } else {
889 sumOfWeightedPositions += ((double)(*tot))*siLocalPosition;
890 sumOfTot += (double)(*tot);
891 }
892 rowMin = std::min(row, rowMin);
893 rowMax = std::max(row, rowMax);
894 colMin = std::min(col, colMin);
895 colMax = std::max(col, colMax);
896
897 }
898 sumOfWeightedPositions /= sumOfTot;
899 //what you want to know is simple:
900 //just the row and column of this average position!
901 InDetDD::SiCellId cellIdWeightedPosition=design->cellIdOfPosition(sumOfWeightedPositions);
902
903 if (!cellIdWeightedPosition.isValid()){
904 ATH_MSG_WARNING(" Weighted position is on invalid CellID." );
905 }
906 int columnWeightedPosition=cellIdWeightedPosition.etaIndex();
907 int rowWeightedPosition=cellIdWeightedPosition.phiIndex();
908 ATH_MSG_VERBOSE(" weighted pos row: " << rowWeightedPosition << " col: " << columnWeightedPosition );
909 int centralIndexX=(m_sizeX-1)/2;
910 int centralIndexY=(m_sizeY-1)/2;
911 if (std::abs(rowWeightedPosition-rowMin)>centralIndexX or
912 std::abs(rowWeightedPosition-rowMax)>centralIndexX){
913 ATH_MSG_VERBOSE(" Cluster too large rowMin" << rowMin << " rowMax " << rowMax << " centralX " << centralIndexX);
914 return input;
915 }
916 if (std::abs(columnWeightedPosition-colMin)>centralIndexY or
917 std::abs(columnWeightedPosition-colMax)>centralIndexY){
918 ATH_MSG_VERBOSE(" Cluster too large colMin" << colMin << " colMax " << colMax << " centralY " << centralIndexY);
919 return input;
920 }
921 input.matrixOfToT.reserve(m_sizeX);
922 for (unsigned int a=0;a<m_sizeX;a++){
923 input.matrixOfToT.emplace_back(m_sizeY, 0.0);
924 }
925 // Seed the pitches for cells with no hit. For ONNX (ITk) take the nominal
926 // from the design, so non-uniform sensors (e.g. 50x50 or 25x100 um) get the
927 // right value; for lwtnn keep the 0.4 eta seed the models were trained with.
928 if (m_useXPitches) {
929 input.vectorOfPitchesY.assign(m_sizeY, design->etaPitch());
930 input.vectorOfPitchesX.assign(m_sizeX, design->phiPitch());
931 } else {
932 input.vectorOfPitchesY.assign(m_sizeY, 0.4);
933 }
934 rdosBegin = rdos.begin();
935 charge = chListRecreated.begin();
936 chargeEnd = chListRecreated.end();
937 tot = totListRecreated.begin();
938 ATH_MSG_VERBOSE(" Putting together the n. " << rdos.size() << " rdos into a matrix." );
939 Identifier pixidentif=pCluster.identify();
940 input.etaModule=(int)pixelID.eta_module(pixidentif);
941 input.ClusterPixLayer=(int)pixelID.layer_disk(pixidentif);
942 input.ClusterPixBarrelEC=(int)pixelID.barrel_ec(pixidentif);
943 for (;( charge != chargeEnd) and (rdosBegin!= rdosEnd); ++rdosBegin, ++charge, ++tot){
944 Identifier rId = *rdosBegin;
945 unsigned int absrow = pixelID.phi_index(rId)-rowWeightedPosition+centralIndexX;
946 unsigned int abscol = pixelID.eta_index(rId)-columnWeightedPosition+centralIndexY;
947 if (absrow > m_sizeX){
948 ATH_MSG_WARNING(" problem with index: " << absrow << " min: " << 0 << " max: " << m_sizeX);
949 return input;
950 }
951 if (abscol > m_sizeY){
952 ATH_MSG_WARNING(" problem with index: " << abscol << " min: " << 0 << " max: " << m_sizeY);
953 return input;
954 }
955 InDetDD::SiCellId cellId = element->cellIdFromIdentifier(*rdosBegin);
956 InDetDD::SiDiodesParameters diodeParameters = design->parameters(cellId);
957 double pitchY = diodeParameters.width().xEta();
958 double pitchX = diodeParameters.width().xPhi();
959 if (not m_useToT) {
960 input.matrixOfToT[absrow][abscol]=*charge;
961 } else {
962 input.matrixOfToT[absrow][abscol]=(double)(*tot);
963 // in case to RunI setup to make IBL studies
964 if(m_doRunI){
965 if (m_addIBL and (input.ClusterPixLayer==0) and (input.ClusterPixBarrelEC==0)){
966 input.matrixOfToT[absrow][abscol]*=3;
967 }
968 }else{
969 // for RunII IBL is always present
970 if ( (input.ClusterPixLayer==0) and (input.ClusterPixBarrelEC==0)){
971 input.matrixOfToT[absrow][abscol]*=3;
972 }
973 }
974
975 }
976 if (m_useXPitches) {
977 input.vectorOfPitchesY[abscol]=pitchY;
978 input.vectorOfPitchesX[absrow]=pitchX;
979 } else if (std::abs(pitchY-0.4)>1e-5){
980 // lwtnn: only override the 0.4 seed for long pixels
981 input.vectorOfPitchesY[abscol]=pitchY;
982 }
983 }//end iteration on rdos
984 ATH_MSG_VERBOSE(" eta module: " << input.etaModule );
985 ATH_MSG_VERBOSE(" Layer number: " << input.ClusterPixLayer << " Barrel / endcap: " << input.ClusterPixBarrelEC );
986 input.useTrackInfo=false;
987 const Amg::Vector2D& prdLocPos = pCluster.localPosition();
988 InDetDD::SiLocalPosition centroid(prdLocPos);
989 Amg::Vector3D globalPos = element->globalPosition(centroid);
990 Amg::Vector3D my_track = globalPos-beamSpotPosition;
991 const Amg::Vector3D &my_normal = element->normal();
992 const Amg::Vector3D &my_phiax = element->phiAxis();
993 const Amg::Vector3D &my_etaax = element->etaAxis();
994 float trkphicomp = my_track.dot(my_phiax);
995 float trketacomp = my_track.dot(my_etaax);
996 float trknormcomp = my_track.dot(my_normal);
997 double bowphi = std::atan2(trkphicomp,trknormcomp);
998 double boweta = std::atan2(trketacomp,trknormcomp);
999 tanl = m_pixelLorentzAngleTool->getTanLorentzAngle(element->identifyHash(), Gaudi::Hive::currentContext());
1000 if(bowphi > M_PI_2) bowphi -= M_PI;
1001 if(bowphi < -M_PI_2) bowphi += M_PI;
1002 int readoutside = design->readoutSide();
1003 double angle = std::atan(std::tan(bowphi)-readoutside*tanl);
1004 input.phi=angle;
1005 ATH_MSG_VERBOSE(" Angle theta bef corr: " << boweta );
1006 if (boweta>M_PI_2) boweta-=M_PI;
1007 if (boweta<-M_PI_2) boweta+=M_PI;
1008 input.theta=boweta;
1009 ATH_MSG_VERBOSE(" Angle phi: " << angle << " theta: " << boweta );
1010 input.rowWeightedPosition=rowWeightedPosition;
1011 input.columnWeightedPosition=columnWeightedPosition;
1012 ATH_MSG_VERBOSE(" RowWeightedPosition: " << rowWeightedPosition << " ColWeightedPosition: " << columnWeightedPosition );
1013 return input;
1014 }//end create NNinput function
1015
1016 size_t
1018 return (m_sizeX * m_sizeY) + m_sizeY + (useTrackInfo ? 4 : 5);
1019 }
1020
1021 // ======================================================================
1022 // ONNX inference methods
1023 // ======================================================================
1024
1025 std::vector<double>
1027 const Eigen::VectorXd& input) const {
1028
1029 std::vector<double> result(3, 0.0);
1031 if (!onnxCollection.isValid()) {
1032 ATH_MSG_FATAL("Failed to get ONNX network collection with key " << m_readKeyONNX.key());
1033 return result;
1034 }
1035 Ort::Session& session = *onnxCollection->numberNetwork;
1036
1037 // Get expected input dimension from the model
1038 auto inputTypeInfo = session.GetInputTypeInfo(0);
1039 auto tensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
1040 const int64_t expectedDim = tensorInfo.GetShape()[1];
1041
1042 // Convert Eigen double vector to float
1043 if (static_cast<int64_t>(input.size()) != expectedDim) {
1044 ATH_MSG_FATAL("ONNX number network expects input dimension " << expectedDim
1045 << " but got " << input.size() << " — check model/configuration");
1046 return result;
1047 }
1048 std::vector<float> inputData(expectedDim);
1049 for (int i = 0; i < expectedDim; ++i) {
1050 inputData[i] = static_cast<float>(input[i]);
1051 }
1052
1053 // Create input tensor
1054 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
1055 std::vector<int64_t> inputShape = {1, expectedDim};
1056 Ort::Value inputTensor = Ort::Value::CreateTensor<float>(
1057 memInfo, inputData.data(), inputData.size(),
1058 inputShape.data(), inputShape.size());
1059 Ort::AllocatorWithDefaultOptions allocator;
1060 auto inputName = session.GetInputNameAllocated(0, allocator);
1061 auto outputName = session.GetOutputNameAllocated(0, allocator);
1062 const char* inputNames[] = {inputName.get()};
1063 const char* outputNames[] = {outputName.get()};
1064
1065 // Run inference
1066 auto outputTensors = session.Run(
1067 Ort::RunOptions{nullptr},
1068 inputNames, &inputTensor, 1,
1069 outputNames, 1);
1070
1071 // Extract output
1072 const float* outputData = outputTensors[0].GetTensorData<float>();
1073 double num0 = outputData[0];
1074 double num1 = outputData[1];
1075 double num2 = outputData[2];
1076
1077 // Normalize
1078 const double sum = num0 + num1 + num2;
1079 if (sum <= 0.0) {
1080 ATH_MSG_WARNING("ONNX number network output sum is non-positive: " << sum);
1081 return result;
1082 }
1083 const double inverseSum = 1.0 / sum;
1084 result[0] = num0 * inverseSum;
1085 result[1] = num1 * inverseSum;
1086 result[2] = num2 * inverseSum;
1087
1088 ATH_MSG_VERBOSE("ONNX Prob of n. particles (1): " << result[0]
1089 << " (2): " << result[1]
1090 << " (3): " << result[2]);
1091 return result;
1092 }
1093
1094 std::vector<Amg::Vector2D>
1096 const Eigen::VectorXd& input,
1097 NNinput& rawInput,
1098 const InDet::PixelCluster& pCluster,
1099 int numberSubClusters,
1100 std::vector<Amg::MatrixX>& errors) const {
1101
1102 std::vector<Amg::Vector2D> allPositions;
1103 if (numberSubClusters < 1 || numberSubClusters > static_cast<int>(m_maxSubClusters)) {
1104 return allPositions;
1105 }
1106
1108 if (!onnxCollection.isValid()) {
1109 ATH_MSG_FATAL("Failed to get ONNX network collection with key " << m_readKeyONNX.key());
1110 return allPositions;
1111 }
1112 Ort::Session* posNet = nullptr;
1113 if (numberSubClusters == 1) posNet = onnxCollection->positionNetwork1.get();
1114 else if (numberSubClusters == 2) posNet = onnxCollection->positionNetwork2.get();
1115 else if (numberSubClusters == 3) posNet = onnxCollection->positionNetwork3.get();
1116
1117 if (!posNet) {
1118 ATH_MSG_FATAL("ONNX position network for " << numberSubClusters
1119 << " sub-clusters not found in collection");
1120 return allPositions;
1121 }
1122
1123 Ort::Session& session = *posNet;
1124
1125 // Get expected input dimension from the model
1126 auto inputTypeInfo = session.GetInputTypeInfo(0);
1127 auto tensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
1128 const int64_t expectedDim = tensorInfo.GetShape()[1];
1129
1130 // Convert input to float
1131 if (static_cast<int64_t>(input.size()) != expectedDim) {
1132 ATH_MSG_FATAL("ONNX position network (" << numberSubClusters
1133 << " sub-clusters) expects input dimension " << expectedDim
1134 << " but got " << input.size() << " — check model/configuration");
1135 return allPositions;
1136 }
1137 std::vector<float> inputData(expectedDim);
1138 for (int i = 0; i < expectedDim; ++i) {
1139 inputData[i] = static_cast<float>(input[i]);
1140 }
1141
1142 // Create input tensor
1143 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
1144 std::vector<int64_t> inputShape = {1, expectedDim};
1145 Ort::Value inputTensor = Ort::Value::CreateTensor<float>(
1146 memInfo, inputData.data(), inputData.size(),
1147 inputShape.data(), inputShape.size());
1148 Ort::AllocatorWithDefaultOptions allocator;
1149 auto inputName = session.GetInputNameAllocated(0, allocator);
1150 auto outputName = session.GetOutputNameAllocated(0, allocator);
1151 const char* inputNames[] = {inputName.get()};
1152 const char* outputNames[] = {outputName.get()};
1153
1154 // Run inference
1155 auto outputTensors = session.Run(
1156 Ort::RunOptions{nullptr},
1157 inputNames, &inputTensor, 1,
1158 outputNames, 1);
1159
1160 // Extract output: expect [1, 5*numberSubClusters]
1161 // Format per sub-cluster: [alpha, mean_x, mean_y, prec_x, prec_y]
1162 const float* outputData = outputTensors[0].GetTensorData<float>();
1163
1164 std::vector<double> positionValues;
1165 positionValues.reserve(numberSubClusters * 2);
1166
1167 for (int iSub = 0; iSub < numberSubClusters; ++iSub) {
1168 const int offset = iSub * 5;
1169 // outputData[offset+0] = alpha (unused)
1170 const double mean_x = outputData[offset + 1];
1171 const double mean_y = outputData[offset + 2];
1172 const double prec_x = outputData[offset + 3];
1173 const double prec_y = outputData[offset + 4];
1174
1175 positionValues.push_back(mean_x);
1176 positionValues.push_back(mean_y);
1177
1178 // Convert precision to RMS and build error matrix
1179 if (prec_x <= 0 || prec_y <= 0) {
1180 ATH_MSG_WARNING("ONNX position network returned non-positive precision for sub-cluster "
1181 << iSub << " (prec_x=" << prec_x << ", prec_y=" << prec_y
1182 << "); using fallback RMS of 0.01");
1183 }
1184 const float rawRmsX = (prec_x > 0) ? std::sqrt(1.0f / prec_x) : 0.01f;
1185 const float rawRmsY = (prec_y > 0) ? std::sqrt(1.0f / prec_y) : 0.01f;
1186 const double rmsX = correctedRMS(rawRmsX, rawInput.vectorOfPitchesX, m_sizeX);
1187 const double rmsY = correctedRMS(rawRmsY, rawInput.vectorOfPitchesY, m_sizeY);
1188
1189 Amg::MatrixX erm(2, 2);
1190 erm.setZero();
1191 erm(0, 0) = rmsX * rmsX;
1192 erm(1, 1) = rmsY * rmsY;
1193 errors.push_back(std::move(erm));
1194 }
1195
1196 // Convert raw position outputs to detector coordinates
1197 allPositions = getPositionsFromOutput(positionValues, rawInput, pCluster);
1198 return allPositions;
1199 }
1200
1201}//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:997
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)