@@ -91,11 +91,20 @@ struct HadronNucleiCorrelation {
9191 Configurable<bool > doQA{" doQA" , true , " save QA histograms" };
9292 Configurable<bool > doMCQA{" doMCQA" , false , " save MC QA histograms" };
9393 Configurable<bool > isMC{" isMC" , false , " is MC" };
94- Configurable<bool > isMCGen{" isMCGen" , false , " is isMCGen" };
9594 Configurable<bool > isPrim{" isPrim" , true , " is isPrim" };
9695 Configurable<bool > doCorrection{" doCorrection" , false , " do efficiency correction" };
9796 Configurable<bool > doQuadraticPID{" doQuadraticPID" , false , " do PID with sum in quadrature of TOF and TPC" };
9897
98+ struct : ConfigurableGroup {
99+ std::string prefix = " Coalescence" ; // JSON group name
100+ Configurable<bool > doMCGenCoalescence{" doMCGenCoalescence" , false , " do a simple coalescence on the generated level" };
101+ // Coalescence parameters used in Eur. Phys. J. A 59 (2023) 72:
102+ // p_max = 0.148 GeV/c
103+ // r_max = 2 fm
104+ Configurable<float > pMax{" pMax" , 0.148 , " maximum momentum for coalescence" };
105+ Configurable<float > rMax{" rMax" , 2.0 , " maximum radius for coalescence" };
106+ } settingsCoalescence;
107+
99108 Configurable<std::string> fCorrectionPath {" fCorrectionPath" , " " , " Correction path to file" };
100109 Configurable<std::string> fCorrectionHisto {" fCorrectionHisto" , " " , " Correction histogram" };
101110 Configurable<std::string> cfgUrl{" cfgUrl" , " http://alice-ccdb.cern.ch" , " url of the ccdb repository" };
@@ -516,11 +525,11 @@ struct HadronNucleiCorrelation {
516525 registry.add (" hReco_Pt_Proton_TPCEl" , " Reco (anti)protons in reco collisions" , {HistType::kTH1F , {ptAxisSmall}});
517526 registry.add (" hReco_Pt_Proton_TPCEl_or_TOF" , " Reco (anti)protons in reco collisions" , {HistType::kTH1F , {ptAxisSmall}});
518527 registry.add (" hReco_Pt_Deuteron_TPCEl" , " Reco (anti)deuterons in reco collisions" , {HistType::kTH1F , {ptAxisSmall}});
519- registry.add (" hReco_Pt_Deuteron_TPCEl_or_TOF" , " Reco (anti)protons in reco collisions" , {HistType::kTH1F , {ptAxisSmall}});
528+ registry.add (" hReco_Pt_Deuteron_TPCEl_or_TOF" , " Reco (anti)deuterons in reco collisions" , {HistType::kTH1F , {ptAxisSmall}});
520529 }
521530 }
522531
523- if (isMCGen ) {
532+ if (doprocessMixedEventGen || doprocessSameEventGen ) {
524533 registry.add (" Generated/hNEventsMC" , " hNEventsMC" , {HistType::kTH1D , {{1 , 0 .f , 1 .f }}});
525534 registry.get <TH1 >(HIST (" Generated/hNEventsMC" ))->GetXaxis ()->SetBinLabel (1 , " All" );
526535
@@ -549,6 +558,8 @@ struct HadronNucleiCorrelation {
549558 registry.add (" Generated/hAntiDeuteronsVsPt" , " hAntiDeuteronsVsPt" , {HistType::kTH1D , {ptAxisGen}});
550559 registry.add (" Generated/hProtonsVsPt" , " hProtonsVsPt" , {HistType::kTH1D , {ptAxisGen}});
551560 registry.add (" Generated/hAntiProtonsVsPt" , " hAntiProtonsVsPt" , {HistType::kTH1D , {ptAxisGen}});
561+ } else if (settingsCoalescence.doMCGenCoalescence .value ) {
562+ LOG (fatal) << " This is not a Gen Run but the Gen coalescence is required. Turn off doMCGenCoalescence" ;
552563 }
553564 }
554565
@@ -1695,10 +1706,222 @@ struct HadronNucleiCorrelation {
16951706
16961707 registry.fill (HIST (" Generated/hNEventsMC" ), 0.5 );
16971708
1709+ // Local representation of a generated particle.
1710+ struct GenCoalescenceCandidate {
1711+ GenCandidate kinematics;
1712+
1713+ int pdg = 0 ;
1714+ float x = 0 .f;
1715+ float y = 0 .f;
1716+ float z = 0 .f;
1717+ float t = 0 .f;
1718+
1719+ bool physicalPrimary = false ;
1720+
1721+ [[nodiscard]] float pt () const { return kinematics.pt (); }
1722+ [[nodiscard]] float eta () const { return kinematics.eta (); }
1723+ [[nodiscard]] float phi () const { return kinematics.phi (); }
1724+ [[nodiscard]] float pz () const { return kinematics.pz (); }
1725+
1726+ [[nodiscard]] float px () const { return pt () * std::cos (phi ()); }
1727+ [[nodiscard]] float py () const { return pt () * std::sin (phi ()); }
1728+
1729+ [[nodiscard]] float mass () const
1730+ {
1731+ switch (std::abs (pdg)) {
1732+ case PDG_t::kProton :
1733+ return o2::track::PID::getMass (o2::track::PID ::Proton);
1734+ case PDG_t::kNeutron :
1735+ return o2::constants::physics::MassNeutron;
1736+ case o2::constants::physics::Pdg::kDeuteron :
1737+ return o2::track::PID::getMass (o2::track::PID ::Deuteron);
1738+ default :
1739+ LOG (fatal) << " Unhandled pdg " << pdg;
1740+ return 0 .f ;
1741+ }
1742+ }
1743+ [[nodiscard]] float energy () const { return std::hypot (kinematics.p (), mass ()); }
1744+ [[nodiscard]] float rapidity () const { return kinematics.rapidityForMass (mass ()); }
1745+ [[nodiscard]] bool isPhysicalPrimary () const { return physicalPrimary; }
1746+ [[nodiscard]] int pdgCode () const { return pdg; }
1747+ };
1748+
1749+ std::vector<GenCoalescenceCandidate> particlesToProcess;
1750+ particlesToProcess.reserve (mcParticles.size ());
1751+
1752+ int64_t localIndex = 0 ;
1753+ for (const auto & particle : mcParticles) {
1754+ switch (particle.pdgCode ()) {
1755+ case PDG_t::kProton :
1756+ case PDG_t::kProtonBar :
1757+ case PDG_t::kNeutron :
1758+ case PDG_t::kNeutronBar :
1759+ case o2::constants::physics::Pdg::kDeuteron :
1760+ case -o2::constants::physics::Pdg::kDeuteron :
1761+ particlesToProcess.push_back ({.kinematics = {particle.pt (), particle.eta (), particle.phi ()},
1762+ .pdg = particle.pdgCode (),
1763+ .x = particle.vx (),
1764+ .y = particle.vy (),
1765+ .z = particle.vz (),
1766+ .t = particle.vt (),
1767+ .physicalPrimary = particle.isPhysicalPrimary ()});
1768+ break ;
1769+ default :
1770+ break ;
1771+ }
1772+ }
1773+
1774+ if (settingsCoalescence.doMCGenCoalescence .value ) {
1775+ std::vector<bool > consumed (particlesToProcess.size (), false );
1776+ std::vector<GenParticle> deuterons;
1777+
1778+ // Try p+n -> d and pbar+nbar -> dbar independently.
1779+ for (const int sign : {+1 , -1 }) {
1780+ const int protonPDG = sign * PDG_t::kProton ;
1781+ const int neutronPDG = sign * PDG_t::kNeutron ;
1782+ const int deuteronPDG = sign * o2::constants::physics::Pdg::kDeuteron ;
1783+
1784+ for (size_t ip = 0 ; ip < particlesToProcess.size (); ++ip) {
1785+ if (consumed[ip] || particlesToProcess[ip].pdgCode () != protonPDG) {
1786+ continue ;
1787+ }
1788+
1789+ const auto & proton = particlesToProcess[ip];
1790+
1791+ for (size_t in = 0 ; in < particlesToProcess.size (); ++in) {
1792+ if (consumed[in] || particlesToProcess[in].pdgCode () != neutronPDG) {
1793+ continue ;
1794+ }
1795+
1796+ const auto & neutron = particlesToProcess[in];
1797+
1798+ const float ep = proton.energy ();
1799+ const float en = neutron.energy ();
1800+
1801+ // Propagate the particle freezing out first to the freeze-out time of the other particle
1802+ float xp = proton.vx ;
1803+ float yp = proton.vy ;
1804+ float zp = proton.vz ;
1805+
1806+ float xn = neutron.vx ;
1807+ float yn = neutron.vy ;
1808+ float zn = neutron.vz ;
1809+
1810+ const float commonTime = std::max (static_cast <float >(proton.vt ), static_cast <float >(neutron.vt ));
1811+
1812+ if (proton.vt < commonTime) {
1813+ const float dt = commonTime - proton.vt ;
1814+ xp += proton.px / ep * dt;
1815+ yp += proton.py / ep * dt;
1816+ zp += proton.pz / ep * dt;
1817+ }
1818+
1819+ if (neutron.vt < commonTime) {
1820+ const float dt = commonTime - neutron.vt ;
1821+ xn += neutron.px / en * dt;
1822+ yn += neutron.py / en * dt;
1823+ zn += neutron.pz / en * dt;
1824+ }
1825+
1826+ // Velocity of the p-n centre-of-mass frame.
1827+ const float totalE = ep + en;
1828+ const float totalPx = proton.px + neutron.px ;
1829+ const float totalPy = proton.py + neutron.py ;
1830+ const float totalPz = proton.pz + neutron.pz ;
1831+
1832+ const float bx = totalPx / totalE;
1833+ const float by = totalPy / totalE;
1834+ const float bz = totalPz / totalE;
1835+
1836+ const float beta2 = bx * bx + by * by + bz * bz;
1837+ if (beta2 >= 1 .) {
1838+ continue ;
1839+ }
1840+
1841+ const float gamma = 1 . / std::sqrt (1 . - beta2);
1842+
1843+ // Boost the proton momentum into the p-n rest frame.
1844+ // In that frame p_p* = -p_n*, therefore |p_p*| is the relative momentum entering the coalescence cut.
1845+ float pxStar = proton.px ;
1846+ float pyStar = proton.py ;
1847+ float pzStar = proton.pz ;
1848+
1849+ if (beta2 > 0 .) {
1850+ const float betaDotP = bx * proton.px + by * proton.py + bz * proton.pz ;
1851+
1852+ const float factor = ((gamma - 1 .) * betaDotP / beta2) - gamma * ep;
1853+
1854+ pxStar += factor * bx;
1855+ pyStar += factor * by;
1856+ pzStar += factor * bz;
1857+ }
1858+
1859+ const float pRelative = std::sqrt (pxStar * pxStar + pyStar * pyStar + pzStar * pzStar);
1860+
1861+ if (pRelative >= settingsCoalescence.pMax .value ) {
1862+ continue ;
1863+ }
1864+
1865+ // The two particles have already been propagated to equal time in the lab. Transform their spatial separation to the pair CM frame.
1866+ float dx = xp - xn;
1867+ float dy = yp - yn;
1868+ float dz = zp - zn;
1869+
1870+ if (beta2 > 0 .) {
1871+ const float betaDotR = bx * dx + by * dy + bz * dz;
1872+ const float factor = (gamma - 1 .) * betaDotR / beta2;
1873+
1874+ dx += factor * bx;
1875+ dy += factor * by;
1876+ dz += factor * bz;
1877+ }
1878+
1879+ const float rRelative = std::sqrt (dx * dx + dy * dy + dz * dz);
1880+
1881+ if (rRelative >= settingsCoalescence.rMax .value ) {
1882+ continue ;
1883+ }
1884+
1885+ // Successful coalescence: remove the constituent proton and neutron and replace them by a deuteron carrying their total three-momentum
1886+ consumed[ip] = true ;
1887+ consumed[in] = true ;
1888+
1889+ deuterons.push_back ({deuteronPDG,
1890+ static_cast <float >(totalPx),
1891+ static_cast <float >(totalPy),
1892+ static_cast <float >(totalPz),
1893+ static_cast <float >(0.5 * (xp + xn)),
1894+ static_cast <float >(0.5 * (yp + yn)),
1895+ static_cast <float >(0.5 * (zp + zn)),
1896+ static_cast <float >(commonTime),
1897+ proton.isPhysicalPrimary () && neutron.isPhysicalPrimary (),
1898+ localIndex++});
1899+ break ;
1900+ }
1901+ }
1902+ }
1903+
1904+ // Construct the post-coalescence particle list.
1905+ std::vector<GenParticle> coalescedParticles;
1906+ coalescedParticles.reserve (particlesToProcess.size () + deuterons.size ());
1907+
1908+ for (size_t i = 0 ; i < particlesToProcess.size (); ++i) {
1909+ if (!consumed[i]) {
1910+ coalescedParticles.push_back (std::move (particlesToProcess[i]));
1911+ }
1912+ }
1913+
1914+ for (auto & deuteron : deuterons) {
1915+ coalescedParticles.push_back (std::move (deuteron));
1916+ }
1917+
1918+ particlesToProcess = std::move (coalescedParticles);
1919+ }
1920+
16981921 // Pairing candidates are collected during the QA loop below (which already visits every particle)
16991922 GenCollisionCache genCache;
17001923
1701- for (const auto & particle : mcParticles ) {
1924+ for (const auto & particle : particlesToProcess ) {
17021925 auto fillGeneratedQa = [this , &particle](const float binPosition) {
17031926 switch (particle.pdgCode ()) {
17041927 case PDG_t::kProton :
0 commit comments