2424#include " Common/CCDB/ctpRateFetcher.h"
2525#include " Common/Core/CollisionTypeHelper.h"
2626#include " Common/Core/PID/TPCPIDResponse.h"
27+ #include " Common/Core/RecoDecay.h"
2728#include " Common/Core/TableHelper.h"
2829#include " Common/DataModel/EventSelection.h"
2930#include " Common/DataModel/PIDResponseTPC.h"
3031#include " Common/TableProducer/PID/pidTPCBase.h" // IWYU pragma: keep
3132#include " Tools/ML/model.h"
3233
34+ #include < CommonConstants/MathConstants.h>
35+ #include < CommonConstants/PhysicsConstants.h>
3336#include < DataFormatsParameters/GRPLHCIFData.h>
3437#include < Framework/AnalysisDataModel.h>
3538#include < Framework/AnalysisHelpers.h>
4649#include < TRandom.h>
4750#include < TString.h>
4851
52+ #include < array>
4953#include < chrono>
5054#include < cstddef>
5155#include < cstdint>
5458#include < memory>
5559#include < ratio>
5660#include < string>
61+ #include < string_view>
5762#include < vector>
5863
5964#include < math.h>
@@ -139,6 +144,9 @@ struct pidTPCConfigurables : o2::framework::ConfigurableGroup {
139144 o2::framework::Configurable<int > useNetworkAl{" useNetworkAl" , 1 , {" Switch for applying neural network on the alpha mass hypothesis (if network enabled) (set to 0 to disable)" }};
140145 o2::framework::Configurable<float > networkBetaGammaCutoff{" networkBetaGammaCutoff" , 0.45 , {" Lower value of beta-gamma to override the NN application" }};
141146 o2::framework::Configurable<std::string> cfgPathGrpLhcIf{" ccdb-path-grplhcif" , " GLO/Config/GRPLHCIF" , " Path on the CCDB for the GRPLHCIF object" };
147+
148+ o2::framework::Configurable<float > phiEntranceCoeff1{" phiEntranceCoeff1" , 1 .026f , " phiEntrance = phi + phiEntranceCoeff1 * LightSpeedDm2PS * 0.5 * phiEntranceCoeff2 * 1/pT[GeV/c]" };
149+ o2::framework::Configurable<float > phiEntranceCoeff2{" phiEntranceCoeff2" , 85 .f , " phiEntrance = phi + phiEntranceCoeff1 * LightSpeedDm2PS * 0.5 * phiEntranceCoeff2 * 1/pT[GeV/c]" };
142150};
143151
144152// helper getter - FIXME should be separate
@@ -445,7 +453,48 @@ class pidTPCModule
445453 template <typename TCCDB , typename M, typename T, typename B>
446454 std::unique_ptr<float []> createNetworkPrediction (TCCDB & ccdb, soa::Join<aod::Collisions, aod::EvSels> const & collisions, M const & mults, T const & tracks, B const & bcs, const size_t size)
447455 {
448- auto start_network_total = std::chrono::high_resolution_clock::now ();
456+ constexpr int NParticleTypes = 9 ;
457+ constexpr double OneToKilo = 1 .e -3 ;
458+ constexpr int NanoToOne = 1000000000 ;
459+ constexpr double MultiplicityNorm = 11000 .;
460+ constexpr double HadronicRateNormPp = 1500 .;
461+ constexpr double HadronicRateNormAa = 50 .;
462+ constexpr double Ft0cOccupancyNorm = 60000 .;
463+ constexpr float LightSpeedDm2PS = o2::constants::physics::LightSpeedCm2PS / 10 .f ;
464+
465+ struct NNVersionEntry {
466+ std::string_view versionName{};
467+ int numberOfFeatures{};
468+ int versionNumber{};
469+ };
470+
471+ constexpr std::array<NNVersionEntry, 6 > NNVersionsDictionary{
472+ {{" " , 6 , 1 },
473+ {" 1" , 6 , 1 },
474+ {" 2" , 7 , 2 },
475+ {" 3" , 8 , 3 },
476+ {" 4" , 9 , 4 },
477+ {" 5" , 9 , 5 }}};
478+
479+ enum IndexNnFeature : int {
480+ IdxTpcInnerParam = 0 ,
481+ IdxTgl,
482+ IdxSigned1Pt,
483+ IdxMass,
484+ IdxMultiplicity,
485+ IdxNClusters,
486+ IdxFt0cOcc,
487+ IdxHadronicRate,
488+ IdxModPhi
489+ };
490+
491+ constexpr int OldestNNVersionWithFt0c{2 };
492+ constexpr int OldestNNVersionWithHadronicRate{3 };
493+ constexpr int OldestNNVersionWithModPhi{4 };
494+ constexpr int NNVersionWithModPhiEntrance{5 };
495+
496+ const auto startNetworkTotal = std::chrono::high_resolution_clock::now ();
497+
449498 if (pidTPCopts.autofetchNetworks ) {
450499 const auto & bc = bcs.begin ();
451500 // Initialise correct TPC response object before NN setup (for NCl normalisation)
@@ -483,7 +532,7 @@ class pidTPCModule
483532
484533 if (bc.timestamp () < network.getValidityFrom () || bc.timestamp () > network.getValidityUntil ()) { // fetches network only if the runnumbers change
485534 LOG (info) << " Fetching network for timestamp: " << bc.timestamp ();
486- bool retrieveSuccess = ccdb->getCCDBAccessor ().retrieveBlob (pidTPCopts.networkPathCCDB .value , " ." , metadata, bc.timestamp (), false , pidTPCopts.networkPathLocally .value , " " , " " , &headers);
535+ const bool retrieveSuccess = ccdb->getCCDBAccessor ().retrieveBlob (pidTPCopts.networkPathCCDB .value , " ." , metadata, bc.timestamp (), false , pidTPCopts.networkPathLocally .value , " " , " " , &headers);
487536 networkVersion = headers[" NN-Version" ];
488537 if (retrieveSuccess) {
489538 network.initModel (pidTPCopts.networkPathLocally .value , pidTPCopts.enableNetworkOptimizations .value , pidTPCopts.networkSetNumThreads .value , strtoul (headers[" Valid-From" ].c_str (), NULL , 0 ), strtoul (headers[" Valid-Until" ].c_str (), NULL , 0 ));
@@ -497,52 +546,52 @@ class pidTPCModule
497546 }
498547
499548 // Defining some network parameters
500- int input_dimensions = network.getNumInputNodes ();
501- int output_dimensions = network.getNumOutputNodes ();
502- const uint64_t track_prop_size = input_dimensions * size;
503- const uint64_t prediction_size = output_dimensions * size;
549+ const int inputDimensions = network.getNumInputNodes ();
550+ const int outputDimensions = network.getNumOutputNodes ();
551+ const uint64_t trackPropSize = inputDimensions * size;
552+ const uint64_t predictionSize = outputDimensions * size;
553+
554+ int nnVersion{0 };
555+ for (const auto & nnVersionEntry : NNVersionsDictionary) {
556+ if (networkVersion == nnVersionEntry.versionName && inputDimensions == nnVersionEntry.numberOfFeatures ) {
557+ nnVersion = nnVersionEntry.versionNumber ;
558+ break ;
559+ }
560+ }
561+ if (nnVersion == 0 ) {
562+ LOG (fatal) << " createNetworkPrediction(): networkVersion '" << networkVersion << " ' and number of features " << inputDimensions << " are not compatible according to NNVersionsDictionary" ;
563+ }
564+
565+ const int hadronicRateNorm = collsys == CollisionSystemType::kCollSyspp ? HadronicRateNormPp : HadronicRateNormAa;
504566
505567 // Deliberately uninitialised: the evaluation loop below writes every element
506568 // (one block per mass hypothesis), so zero-initialising would only touch
507569 // every page of an O(100 MB) buffer twice.
508- std::unique_ptr<float []> network_prediction (new float [prediction_size * 9 ]); // For each mass hypotheses
570+ std::unique_ptr<float []> networkPrediction (new float [predictionSize * NParticleTypes]); // For each mass hypotheses
571+
509572 const float nNclNormalization = response->GetNClNormalization ();
510- float duration_network = 0 ;
573+ float durationNetwork = 0 ;
511574
512- std::vector<float > track_properties (track_prop_size );
513- uint64_t counter_track_props = 0 ;
514- int loop_counter = 0 ;
575+ std::vector<float > trackProperties (trackPropSize );
576+ uint64_t counterTrackProps = 0 ;
577+ int loopCounter = 0 ;
515578
516579 // To load the Hadronic rate once for each collision
517- float hadronicRateBegin = 0 .;
518580 std::vector<float > hadronicRateForCollision (collisions.size (), 0 .0f );
519- size_t i = 0 ;
581+ size_t iCollision = 0 ;
520582 for (const auto & collision : collisions) {
521583 const auto & bc = collision.template bc_as <B>();
522584 if (irSource.compare (" " ) != 0 ) {
523- hadronicRateForCollision[i] = mRateFetcher .fetch (ccdb.service , bc.timestamp (), bc.runNumber (), irSource) * 1 .e -3 ;
524- } else {
525- hadronicRateForCollision[i] = 0 .0f ;
585+ hadronicRateForCollision[iCollision] = mRateFetcher .fetch (ccdb.service , bc.timestamp (), bc.runNumber (), irSource) * OneToKilo;
526586 }
527- i++;
528- }
529- auto bc = bcs.begin ();
530- if (irSource.compare (" " ) != 0 ) {
531- hadronicRateBegin = mRateFetcher .fetch (ccdb.service , bc.timestamp (), bc.runNumber (), irSource) * 1 .e -3 ; // kHz
532- } else {
533- hadronicRateBegin = 0 .0f ;
587+ ++iCollision;
534588 }
589+ const auto bc = bcs.begin ();
590+ const float hadronicRateBegin = irSource.compare (" " ) != 0 ? mRateFetcher .fetch (ccdb.service , bc.timestamp (), bc.runNumber (), irSource) * OneToKilo : 0 .f ;
535591
536592 // Filling a std::vector<float> to be evaluated by the network
537593 // Evaluation on single tracks brings huge overhead: Thus evaluation is done on one large vector
538- static constexpr int NParticleTypes = 9 ;
539- constexpr int ExpectedInputDimensionsNNV2 = 7 ;
540- constexpr int ExpectedInputDimensionsNNV3 = 8 ;
541- constexpr int ExpectedInputDimensionsNNV4 = 9 ;
542- constexpr auto NetworkVersionV2 = " 2" ;
543- constexpr auto NetworkVersionV3 = " 3" ;
544- constexpr auto NetworkVersionV4 = " 4" ;
545- for (int j = 0 ; j < NParticleTypes; j++) { // Loop over particle number for which network correction is used
594+ for (int jParticleType = 0 ; jParticleType < NParticleTypes; ++jParticleType) { // Loop over particle number for which network correction is used
546595 for (auto const & trk : tracks) {
547596 if (!trk.hasTPC ()) {
548597 continue ;
@@ -552,74 +601,50 @@ class pidTPCModule
552601 continue ;
553602 }
554603 }
555- track_properties[counter_track_props] = trk.tpcInnerParam ();
556- track_properties[counter_track_props + 1 ] = trk.tgl ();
557- track_properties[counter_track_props + 2 ] = trk.signed1Pt ();
558- track_properties[counter_track_props + 3 ] = o2::track::pid_constants::sMasses [j];
559- track_properties[counter_track_props + 4 ] = (trk.has_collision () && mults.size () > 0 ) ? mults[trk.collisionId ()] / 11000 . : 1 .;
560- track_properties[counter_track_props + 5 ] = std::sqrt (nNclNormalization / trk.tpcNClsFound ());
561- if (input_dimensions == ExpectedInputDimensionsNNV2 && networkVersion == NetworkVersionV2) {
562- track_properties[counter_track_props + 6 ] = (trk.has_collision () && mults.size () > 0 ) ? collisions.iteratorAt (trk.collisionId ()).ft0cOccupancyInTimeRange () / 60000 . : 1 .;
604+ const bool isGoodTrack = trk.has_collision () && mults.size () > 0 ;
605+ trackProperties[counterTrackProps + IdxTpcInnerParam] = trk.tpcInnerParam ();
606+ trackProperties[counterTrackProps + IdxTgl] = trk.tgl ();
607+ trackProperties[counterTrackProps + IdxSigned1Pt] = trk.signed1Pt ();
608+ trackProperties[counterTrackProps + IdxMass] = o2::track::pid_constants::sMasses [jParticleType];
609+ trackProperties[counterTrackProps + IdxMultiplicity] = isGoodTrack ? mults[trk.collisionId ()] / MultiplicityNorm : 1 .;
610+ trackProperties[counterTrackProps + IdxNClusters] = std::sqrt (nNclNormalization / trk.tpcNClsFound ());
611+ if (nnVersion >= OldestNNVersionWithFt0c) {
612+ trackProperties[counterTrackProps + IdxFt0cOcc] = isGoodTrack ? collisions.iteratorAt (trk.collisionId ()).ft0cOccupancyInTimeRange () / Ft0cOccupancyNorm : 1 .;
563613 }
564- if (input_dimensions == ExpectedInputDimensionsNNV3 && networkVersion == NetworkVersionV3) {
565- track_properties[counter_track_props + 6 ] = (trk.has_collision () && mults.size () > 0 ) ? collisions.iteratorAt (trk.collisionId ()).ft0cOccupancyInTimeRange () / 60000 . : 1 .;
566- if (trk.has_collision () && mults.size () > 0 ) {
567- if (collsys == CollisionSystemType::kCollSyspp ) {
568- track_properties[counter_track_props + 7 ] = hadronicRateForCollision[trk.collisionId ()] / 1500 .;
569- } else {
570- track_properties[counter_track_props + 7 ] = hadronicRateForCollision[trk.collisionId ()] / 50 .;
571- }
572- } else {
573- // asign Hadronic Rate at beginning of run if track does not belong to a collision
574- if (collsys == CollisionSystemType::kCollSyspp ) {
575- track_properties[counter_track_props + 7 ] = hadronicRateBegin / 1500 .;
576- } else {
577- track_properties[counter_track_props + 7 ] = hadronicRateBegin / 50 .;
578- }
579- }
614+ if (nnVersion >= OldestNNVersionWithHadronicRate) {
615+ const float hadronicRate = isGoodTrack ? hadronicRateForCollision[trk.collisionId ()] : hadronicRateBegin;
616+ trackProperties[counterTrackProps + IdxHadronicRate] = hadronicRate / hadronicRateNorm;
580617 }
581-
582- if (input_dimensions == ExpectedInputDimensionsNNV4 && networkVersion == NetworkVersionV4) {
583- track_properties[counter_track_props + 6 ] = (trk.has_collision () && mults.size () > 0 ) ? collisions.iteratorAt (trk.collisionId ()).ft0cOccupancyInTimeRange () / 60000 . : 1 .;
584- if (trk.has_collision () && mults.size () > 0 ) {
585- if (collsys == CollisionSystemType::kCollSyspp ) {
586- track_properties[counter_track_props + 7 ] = hadronicRateForCollision[trk.collisionId ()] / 1500 .;
587- } else {
588- track_properties[counter_track_props + 7 ] = hadronicRateForCollision[trk.collisionId ()] / 50 .;
589- }
590- } else {
591- // asign Hadronic Rate at beginning of run if track does not belong to a collision
592- if (collsys == CollisionSystemType::kCollSyspp ) {
593- track_properties[counter_track_props + 7 ] = hadronicRateBegin / 1500 .;
594- } else {
595- track_properties[counter_track_props + 7 ] = hadronicRateBegin / 50 .;
596- }
618+ if (nnVersion >= OldestNNVersionWithModPhi) {
619+ float phi = trk.phi ();
620+ if (nnVersion == NNVersionWithModPhiEntrance) {
621+ phi += pidTPCopts.phiEntranceCoeff1 * LightSpeedDm2PS * 0.5 * pidTPCopts.phiEntranceCoeff2 * trk.signed1Pt ();
597622 }
598- track_properties[counter_track_props + 8 ] = std::fmod ( std::fmod (trk. phi (), 2 * M_PI ) + 2 * M_PI , M_PI / 9.0 );
623+ trackProperties[counterTrackProps + IdxModPhi ] = RecoDecay::constrainAngle ( phi, 0 . f , o2::constants::math::NSectors );
599624 }
600- counter_track_props += input_dimensions ;
625+ counterTrackProps += inputDimensions ;
601626 }
602627
603- auto start_network_eval = std::chrono::high_resolution_clock::now ();
604- float * output_network = network.evalModel (track_properties );
605- auto stop_network_eval = std::chrono::high_resolution_clock::now ();
606- duration_network += std::chrono::duration<float , std::ratio<1 , 1000000000 >>(stop_network_eval - start_network_eval ).count ();
607- for (uint64_t k = 0 ; k < prediction_size; k += output_dimensions ) {
608- for (int l = 0 ; l < output_dimensions; l++ ) {
609- network_prediction[k + l + prediction_size * loop_counter ] = output_network[k + l ];
628+ const auto startNetworkEval = std::chrono::high_resolution_clock::now ();
629+ const float * const outputNetwork = network.evalModel (trackProperties );
630+ const auto stopNetworkEval = std::chrono::high_resolution_clock::now ();
631+ durationNetwork += std::chrono::duration<float , std::ratio<1 , NanoToOne >>(stopNetworkEval - startNetworkEval ).count ();
632+ for (uint64_t kPrediction = 0 ; kPrediction < predictionSize; kPrediction += outputDimensions ) {
633+ for (int lOutputDim = 0 ; lOutputDim < outputDimensions; ++lOutputDim ) {
634+ networkPrediction[ kPrediction + lOutputDim + predictionSize * loopCounter ] = outputNetwork[ kPrediction + lOutputDim ];
610635 }
611636 }
612637
613- counter_track_props = 0 ;
614- loop_counter += 1 ;
638+ counterTrackProps = 0 ;
639+ ++loopCounter ;
615640 }
616- track_properties .clear ();
641+ trackProperties .clear ();
617642
618- auto stop_network_total = std::chrono::high_resolution_clock::now ();
619- LOG (debug) << " Neural Network for the TPC PID response correction: Time per track (eval ONNX): " << duration_network / (size * 9 ) << " ns ; Total time (eval ONNX): " << duration_network / 1000000000 << " s" ;
620- LOG (debug) << " Neural Network for the TPC PID response correction: Time per track (eval + overhead): " << std::chrono::duration<float , std::ratio<1 , 1000000000 >>(stop_network_total - start_network_total ).count () / (size * 9 ) << " ns ; Total time (eval + overhead): " << std::chrono::duration<float , std::ratio<1 , 1000000000 >>(stop_network_total - start_network_total ).count () / 1000000000 << " s" ;
643+ const auto stopNetworkTotal = std::chrono::high_resolution_clock::now ();
644+ LOG (debug) << " Neural Network for the TPC PID response correction: Time per track (eval ONNX): " << durationNetwork / (size * NParticleTypes ) << " ns ; Total time (eval ONNX): " << durationNetwork / NanoToOne << " s" ;
645+ LOG (debug) << " Neural Network for the TPC PID response correction: Time per track (eval + overhead): " << std::chrono::duration<float , std::ratio<1 , NanoToOne >>(stopNetworkTotal - startNetworkTotal ).count () / (size * NParticleTypes ) << " ns ; Total time (eval + overhead): " << std::chrono::duration<float , std::ratio<1 , NanoToOne >>(stopNetworkTotal - startNetworkTotal ).count () / NanoToOne << " s" ;
621646
622- return network_prediction ;
647+ return networkPrediction ;
623648 }
624649
625650 // __________________________________________________
0 commit comments