@@ -574,9 +574,177 @@ struct MultiplicitySelector {
574574 PROCESS_SWITCH (MultiplicitySelector, processMCGen, " Select MC particle count as multiplicity" , false );
575575};
576576
577+ struct EventShapeProducer {
578+ Produces<aod::CFEventShapes> OutputEventShape;
579+
580+ O2_DEFINE_CONFIGURABLE (minTracks, int , 10 , " Minimum number of tracks per collision required to estimate spherocity" );
581+ O2_DEFINE_CONFIGURABLE (minPtTrack, float , 0.2 , " Minimum pT of tracks required to estimate spherocity" );
582+ O2_DEFINE_CONFIGURABLE (AbsEtaTrack, float , 0.8 , " Maximum |eta| of tracks required to estimate spherocity" );
583+ O2_DEFINE_CONFIGURABLE (usePtWeighted, bool , false , " Use pt-weighted spherocity" );
584+ O2_DEFINE_CONFIGURABLE (outputQAHistos, bool , false , " Whether to output QA histograms for spherocity" );
585+
586+ HistogramRegistry QAhistos{" QAhistos" , {}, OutputObjHandlingPolicy::AnalysisObject, false , true };
587+ HistogramRegistry QAhistosCent{" QAhistosCent" , {}, OutputObjHandlingPolicy::AnalysisObject, false , true };
588+ HistogramRegistry QAhistosSpherocity{" QAhistosSpherocity" , {}, OutputObjHandlingPolicy::AnalysisObject, false , true };
589+
590+ std::vector<float > nx, ny, px, py;
591+
592+ void init (InitContext const &)
593+ {
594+ AxisSpec axisPt = {100 , 0.0 , 50.0 };
595+ AxisSpec axisEta = {100 , -1.2 , 1.2 };
596+ AxisSpec axisPhi = {100 , 0.0 , 2.0 * M_PI};
597+ AxisSpec axisNtracks = {100 , 0.0 , 200.0 };
598+ AxisSpec axisMult = {100 , 0.0 , 100.0 };
599+ AxisSpec axisSpherocity = {1000 , 0.0 , 1.0 };
600+
601+ QAhistos.add (" multS0" , " " ,{HistType::kTH2F ,{axisMult, axisSpherocity}});
602+ QAhistos.add (" spherocity" , " " , {HistType::kTH1F , {axisSpherocity}});
603+
604+ if (!outputQAHistos) return ;
605+ QAhistos.add (" pt" , " " , {HistType::kTH1F , {axisPt}});
606+ QAhistos.add (" eta" , " " , {HistType::kTH1F , {axisEta}});
607+ QAhistos.add (" phi" , " " , {HistType::kTH1F , {axisPhi}});
608+ QAhistos.add (" ntracks" , " " , {HistType::kTH1F , {axisNtracks}});
609+ QAhistos.add (" mult" , " " , {HistType::kTH1F , {axisMult}});
610+ QAhistos.add (" etaphi" , " " , {HistType::kTH2F , {axisPhi, axisEta}});
611+
612+ QAhistosCent.add (" ntracks_0_20" , " " , {HistType::kTH1F , {axisNtracks}});
613+ QAhistosCent.add (" ntracks_20_40" , " " , {HistType::kTH1F , {axisNtracks}});
614+ QAhistosCent.add (" ntracks_40_60" , " " , {HistType::kTH1F , {axisNtracks}});
615+ QAhistosCent.add (" ntracks_60_100" , " " , {HistType::kTH1F , {axisNtracks}});
616+
617+ QAhistosSpherocity.add (" spherocity_0_20" , " " , {HistType::kTH1F , {axisSpherocity}});
618+ QAhistosSpherocity.add (" spherocity_20_40" , " " , {HistType::kTH1F , {axisSpherocity}});
619+ QAhistosSpherocity.add (" spherocity_40_60" , " " , {HistType::kTH1F , {axisSpherocity}});
620+ QAhistosSpherocity.add (" spherocity_60_100" , " " , {HistType::kTH1F , {axisSpherocity}});
621+ }
622+
623+ // Spherocity calculation based on the central barrel tracks
624+ template <typename TTracks>
625+ float CalculateSpherocity (TTracks const & tracks, bool usePtWeighted, bool outputQAHistos)
626+ {
627+ const size_t nTracks = tracks.size ();
628+ if (nTracks < static_cast <size_t >(minTracks)) return -2 .0f ;
629+
630+ if (nx.size () < nTracks) {
631+ nx.resize (nTracks);
632+ ny.resize (nTracks);
633+ px.resize (nTracks);
634+ py.resize (nTracks);
635+ }
636+
637+ float sumPt = 0 .0f ;
638+ float retval = 999 .0f ;
639+
640+ size_t idx = 0 ;
641+ for (auto & track : tracks) {
642+ const float cosPhi = std::cos (track.phi ());
643+ const float sinPhi = std::sin (track.phi ());
644+
645+ nx[idx] = cosPhi;
646+ ny[idx] = sinPhi;
647+
648+ if (usePtWeighted) {
649+ const float pt = track.pt ();
650+ px[idx] = pt * cosPhi;
651+ py[idx] = pt * sinPhi;
652+ sumPt += pt;
653+ } else {
654+ px[idx] = cosPhi;
655+ py[idx] = sinPhi;
656+ sumPt += 1 .0f ;
657+ }
658+ if (outputQAHistos) {
659+ QAhistos.fill (HIST (" pt" ), track.pt ());
660+ QAhistos.fill (HIST (" eta" ), track.eta ());
661+ QAhistos.fill (HIST (" phi" ), track.phi ());
662+ QAhistos.fill (HIST (" etaphi" ), track.phi (), track.eta ());
663+ }
664+ idx++;
665+ }
666+
667+ if (sumPt == 0 .0f ) return -1 .5f ; // no tracks -- avoid division by zero
668+
669+ // Validation check for non-weighted case
670+ if (!usePtWeighted && std::abs (static_cast <float >(nTracks) - sumPt) > 0 .01f ) {
671+ LOGF (info, " Spherocity calculation: number of tracks (%zu) does not match sum of pT (%f)" , nTracks, sumPt);
672+ return -1 .0f ;
673+ }
674+
675+ for (size_t i = 0 ; i < nTracks; i++) {
676+ float numerator = 0 .0f ;
677+ const float nyi = ny[i];
678+ const float nxi = nx[i];
679+
680+ for (size_t j = 0 ; j < nTracks; j++) {
681+ numerator += std::abs (nyi * px[j] - nxi * py[j]);
682+ }
683+
684+ const float sFull = std::pow (numerator / sumPt, 2 );
685+ if (sFull < retval) retval = sFull ;
686+ }
687+
688+ retval = retval * M_PI * M_PI / 4 .0f ; // normalization factor
689+
690+ if (retval < 0 .0f || retval > 1 .0f ) {
691+ LOGF (info, " Spherocity value is out of range: %f" , retval);
692+ return -0 .5f ;
693+ }
694+
695+ return retval;
696+ }// spherocity calculation ends
697+
698+ Filter cftrackFilter = (aod::cftrack::pt > minPtTrack) && (nabs(aod::cftrack::eta) < AbsEtaTrack);
699+
700+ using myCollisions = aod::CFCollisions;
701+ using myTracks = soa::Filtered<aod::CFTracks>;
702+
703+ inline int getCentralityBin (float multiplicity) {
704+ if (multiplicity < 20 .0f ) return 0 ;
705+ else if (multiplicity < 40 .0f ) return 1 ;
706+ else if (multiplicity < 60 .0f ) return 2 ;
707+ else if (multiplicity < 100 .0f ) return 3 ;
708+ return -1 ; // outside range
709+ }
710+
711+ void processSpherocityData (myCollisions::iterator const & coll, myTracks const & tracks)
712+ {
713+ float spherocity = CalculateSpherocity (tracks, usePtWeighted, outputQAHistos);
714+ OutputEventShape (spherocity);
715+ const float multiplicity = coll.multiplicity ();
716+ QAhistos.fill (HIST (" multS0" ), multiplicity, spherocity);
717+ QAhistos.fill (HIST (" spherocity" ), spherocity);
718+
719+ if (!outputQAHistos) return ;
720+
721+ const size_t nTracks = tracks.size ();
722+ const int centralityBin = getCentralityBin (multiplicity);
723+
724+ QAhistos.fill (HIST (" ntracks" ), nTracks);
725+ QAhistos.fill (HIST (" mult" ), multiplicity);
726+
727+ if (centralityBin == 0 ) {
728+ QAhistosCent.fill (HIST (" ntracks_0_20" ), nTracks);
729+ QAhistosSpherocity.fill (HIST (" spherocity_0_20" ), spherocity);
730+ } else if (centralityBin == 1 ) {
731+ QAhistosCent.fill (HIST (" ntracks_20_40" ), nTracks);
732+ QAhistosSpherocity.fill (HIST (" spherocity_20_40" ), spherocity);
733+ } else if (centralityBin == 2 ) {
734+ QAhistosCent.fill (HIST (" ntracks_40_60" ), nTracks);
735+ QAhistosSpherocity.fill (HIST (" spherocity_40_60" ), spherocity);
736+ } else if (centralityBin == 3 ) {
737+ QAhistosCent.fill (HIST (" ntracks_60_100" ), nTracks);
738+ QAhistosSpherocity.fill (HIST (" spherocity_60_100" ), spherocity);
739+ }
740+ }
741+ PROCESS_SWITCH (EventShapeProducer, processSpherocityData, " Spherocity analysis for Data" , false );
742+ };
743+
577744WorkflowSpec defineDataProcessing (ConfigContext const & cfgc)
578745{
579746 return WorkflowSpec{
580747 adaptAnalysisTask<FilterCF>(cfgc),
581- adaptAnalysisTask<MultiplicitySelector>(cfgc)};
748+ adaptAnalysisTask<MultiplicitySelector>(cfgc),
749+ adaptAnalysisTask<EventShapeProducer>(cfgc)};
582750}
0 commit comments