|
46 | 46 | #include <TParticlePDG.h> |
47 | 47 | #include <TString.h> |
48 | 48 |
|
| 49 | +#include <algorithm> |
49 | 50 | #include <chrono> |
50 | 51 | #include <cmath> |
51 | 52 | #include <cstddef> |
@@ -91,11 +92,20 @@ struct HadronNucleiCorrelation { |
91 | 92 | Configurable<bool> doQA{"doQA", true, "save QA histograms"}; |
92 | 93 | Configurable<bool> doMCQA{"doMCQA", false, "save MC QA histograms"}; |
93 | 94 | Configurable<bool> isMC{"isMC", false, "is MC"}; |
94 | | - Configurable<bool> isMCGen{"isMCGen", false, "is isMCGen"}; |
95 | 95 | Configurable<bool> isPrim{"isPrim", true, "is isPrim"}; |
96 | 96 | Configurable<bool> doCorrection{"doCorrection", false, "do efficiency correction"}; |
97 | 97 | Configurable<bool> doQuadraticPID{"doQuadraticPID", false, "do PID with sum in quadrature of TOF and TPC"}; |
98 | 98 |
|
| 99 | + struct : ConfigurableGroup { |
| 100 | + std::string prefix = "Coalescence"; // JSON group name |
| 101 | + Configurable<bool> doMCGenCoalescence{"doMCGenCoalescence", false, "do a simple coalescence on the generated level"}; |
| 102 | + // Coalescence parameters used in Eur. Phys. J. A 59 (2023) 72: |
| 103 | + // p_max = 0.148 GeV/c |
| 104 | + // r_max = 2 fm |
| 105 | + Configurable<float> pMax{"pMax", 0.148, "maximum momentum for coalescence"}; |
| 106 | + Configurable<float> rMax{"rMax", 2.0, "maximum radius for coalescence"}; |
| 107 | + } settingsCoalescence; |
| 108 | + |
99 | 109 | Configurable<std::string> fCorrectionPath{"fCorrectionPath", "", "Correction path to file"}; |
100 | 110 | Configurable<std::string> fCorrectionHisto{"fCorrectionHisto", "", "Correction histogram"}; |
101 | 111 | Configurable<std::string> cfgUrl{"cfgUrl", "http://alice-ccdb.cern.ch", "url of the ccdb repository"}; |
@@ -516,11 +526,11 @@ struct HadronNucleiCorrelation { |
516 | 526 | registry.add("hReco_Pt_Proton_TPCEl", "Reco (anti)protons in reco collisions", {HistType::kTH1F, {ptAxisSmall}}); |
517 | 527 | registry.add("hReco_Pt_Proton_TPCEl_or_TOF", "Reco (anti)protons in reco collisions", {HistType::kTH1F, {ptAxisSmall}}); |
518 | 528 | 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}}); |
| 529 | + registry.add("hReco_Pt_Deuteron_TPCEl_or_TOF", "Reco (anti)deuterons in reco collisions", {HistType::kTH1F, {ptAxisSmall}}); |
520 | 530 | } |
521 | 531 | } |
522 | 532 |
|
523 | | - if (isMCGen) { |
| 533 | + if (doprocessMixedEventGen || doprocessSameEventGen) { |
524 | 534 | registry.add("Generated/hNEventsMC", "hNEventsMC", {HistType::kTH1D, {{1, 0.f, 1.f}}}); |
525 | 535 | registry.get<TH1>(HIST("Generated/hNEventsMC"))->GetXaxis()->SetBinLabel(1, "All"); |
526 | 536 |
|
@@ -549,6 +559,8 @@ struct HadronNucleiCorrelation { |
549 | 559 | registry.add("Generated/hAntiDeuteronsVsPt", "hAntiDeuteronsVsPt", {HistType::kTH1D, {ptAxisGen}}); |
550 | 560 | registry.add("Generated/hProtonsVsPt", "hProtonsVsPt", {HistType::kTH1D, {ptAxisGen}}); |
551 | 561 | registry.add("Generated/hAntiProtonsVsPt", "hAntiProtonsVsPt", {HistType::kTH1D, {ptAxisGen}}); |
| 562 | + } else if (settingsCoalescence.doMCGenCoalescence.value) { |
| 563 | + LOG(fatal) << "This is not a Gen Run but the Gen coalescence is required. Turn off doMCGenCoalescence"; |
552 | 564 | } |
553 | 565 | } |
554 | 566 |
|
@@ -1695,10 +1707,253 @@ struct HadronNucleiCorrelation { |
1695 | 1707 |
|
1696 | 1708 | registry.fill(HIST("Generated/hNEventsMC"), 0.5); |
1697 | 1709 |
|
| 1710 | + // Local representation of a generated particle. |
| 1711 | + struct GenCoalescenceCandidate { |
| 1712 | + GenCoalescenceCandidate(float pt, |
| 1713 | + float eta, |
| 1714 | + float phi, |
| 1715 | + int pdg, |
| 1716 | + float vx, |
| 1717 | + float vy, |
| 1718 | + float vz, |
| 1719 | + float vt, |
| 1720 | + bool physicalPrimary) |
| 1721 | + : mKinematics{pt, eta, phi}, |
| 1722 | + mPdg{pdg}, |
| 1723 | + mVx{vx}, |
| 1724 | + mVy{vy}, |
| 1725 | + mVz{vz}, |
| 1726 | + mVt{vt}, |
| 1727 | + mPhysicalPrimary{physicalPrimary} |
| 1728 | + { |
| 1729 | + } |
| 1730 | + [[nodiscard]] float pt() const { return mKinematics.pt(); } |
| 1731 | + [[nodiscard]] float eta() const { return mKinematics.eta(); } |
| 1732 | + [[nodiscard]] float phi() const { return mKinematics.phi(); } |
| 1733 | + |
| 1734 | + [[nodiscard]] float px() const { return pt() * std::cos(phi()); } |
| 1735 | + [[nodiscard]] float py() const { return pt() * std::sin(phi()); } |
| 1736 | + [[nodiscard]] float pz() const { return mKinematics.pz(); } |
| 1737 | + |
| 1738 | + [[nodiscard]] float vx() const { return mVx; } |
| 1739 | + [[nodiscard]] float vy() const { return mVy; } |
| 1740 | + [[nodiscard]] float vz() const { return mVz; } |
| 1741 | + [[nodiscard]] float vt() const { return mVt; } |
| 1742 | + |
| 1743 | + [[nodiscard]] float mass() const |
| 1744 | + { |
| 1745 | + switch (std::abs(mPdg)) { |
| 1746 | + case PDG_t::kProton: |
| 1747 | + return o2::track::PID::getMass(o2::track::PID::Proton); |
| 1748 | + case PDG_t::kNeutron: |
| 1749 | + return o2::constants::physics::MassNeutron; |
| 1750 | + case o2::constants::physics::Pdg::kDeuteron: |
| 1751 | + return o2::track::PID::getMass(o2::track::PID::Deuteron); |
| 1752 | + default: |
| 1753 | + LOG(fatal) << "Unhandled pdg " << mPdg; |
| 1754 | + return 0.f; |
| 1755 | + } |
| 1756 | + } |
| 1757 | + [[nodiscard]] float energy() const { return std::hypot(mKinematics.p(), mass()); } |
| 1758 | + [[nodiscard]] float rapidity() const { return mKinematics.rapidityForMass(mass()); } |
| 1759 | + [[nodiscard]] float y() const { return rapidity(); } |
| 1760 | + [[nodiscard]] bool isPhysicalPrimary() const { return mPhysicalPrimary; } |
| 1761 | + [[nodiscard]] int pdgCode() const { return mPdg; } |
| 1762 | + |
| 1763 | + private: |
| 1764 | + GenCandidate mKinematics; |
| 1765 | + |
| 1766 | + int mPdg = 0; |
| 1767 | + float mVx = 0.f; |
| 1768 | + float mVy = 0.f; |
| 1769 | + float mVz = 0.f; |
| 1770 | + float mVt = 0.f; |
| 1771 | + |
| 1772 | + bool mPhysicalPrimary = false; |
| 1773 | + }; |
| 1774 | + |
| 1775 | + std::vector<GenCoalescenceCandidate> particlesToProcess; |
| 1776 | + particlesToProcess.reserve(mcParticles.size()); |
| 1777 | + |
| 1778 | + for (const auto& particle : mcParticles) { |
| 1779 | + switch (particle.pdgCode()) { |
| 1780 | + case PDG_t::kProton: |
| 1781 | + case PDG_t::kProtonBar: |
| 1782 | + case PDG_t::kNeutron: |
| 1783 | + case PDG_t::kNeutronBar: |
| 1784 | + case o2::constants::physics::Pdg::kDeuteron: |
| 1785 | + case -o2::constants::physics::Pdg::kDeuteron: |
| 1786 | + particlesToProcess.emplace_back(particle.pt(), |
| 1787 | + particle.eta(), |
| 1788 | + particle.phi(), |
| 1789 | + particle.pdgCode(), |
| 1790 | + particle.vx(), |
| 1791 | + particle.vy(), |
| 1792 | + particle.vz(), |
| 1793 | + particle.vt(), |
| 1794 | + particle.isPhysicalPrimary()); |
| 1795 | + break; |
| 1796 | + default: |
| 1797 | + break; |
| 1798 | + } |
| 1799 | + } |
| 1800 | + |
| 1801 | + if (settingsCoalescence.doMCGenCoalescence.value) { |
| 1802 | + std::vector<bool> consumed(particlesToProcess.size(), false); |
| 1803 | + std::vector<GenCoalescenceCandidate> deuterons; |
| 1804 | + |
| 1805 | + // Try p+n -> d and pbar+nbar -> dbar independently. |
| 1806 | + for (const int sign : {+1, -1}) { |
| 1807 | + const int protonPDG = sign * PDG_t::kProton; |
| 1808 | + const int neutronPDG = sign * PDG_t::kNeutron; |
| 1809 | + const int deuteronPDG = sign * o2::constants::physics::Pdg::kDeuteron; |
| 1810 | + |
| 1811 | + for (size_t ip = 0; ip < particlesToProcess.size(); ++ip) { |
| 1812 | + if (consumed[ip] || particlesToProcess[ip].pdgCode() != protonPDG) { |
| 1813 | + continue; |
| 1814 | + } |
| 1815 | + |
| 1816 | + const auto& proton = particlesToProcess[ip]; |
| 1817 | + |
| 1818 | + for (size_t in = 0; in < particlesToProcess.size(); ++in) { |
| 1819 | + if (consumed[in] || particlesToProcess[in].pdgCode() != neutronPDG) { |
| 1820 | + continue; |
| 1821 | + } |
| 1822 | + |
| 1823 | + const auto& neutron = particlesToProcess[in]; |
| 1824 | + |
| 1825 | + const float ep = proton.energy(); |
| 1826 | + const float en = neutron.energy(); |
| 1827 | + |
| 1828 | + // Propagate the particle freezing out first to the freeze-out time of the other particle |
| 1829 | + float xp = proton.vx(); |
| 1830 | + float yp = proton.vy(); |
| 1831 | + float zp = proton.vz(); |
| 1832 | + const float tp = proton.vt(); |
| 1833 | + |
| 1834 | + float xn = neutron.vx(); |
| 1835 | + float yn = neutron.vy(); |
| 1836 | + float zn = neutron.vz(); |
| 1837 | + const float tn = neutron.vt(); |
| 1838 | + |
| 1839 | + const float commonTime = std::max(static_cast<float>(tp), static_cast<float>(tn)); |
| 1840 | + |
| 1841 | + if (tp < commonTime) { |
| 1842 | + const float dt = commonTime - tp; |
| 1843 | + xp += proton.px() / ep * dt; |
| 1844 | + yp += proton.py() / ep * dt; |
| 1845 | + zp += proton.pz() / ep * dt; |
| 1846 | + } |
| 1847 | + |
| 1848 | + if (tn < commonTime) { |
| 1849 | + const float dt = commonTime - tn; |
| 1850 | + xn += neutron.px() / en * dt; |
| 1851 | + yn += neutron.py() / en * dt; |
| 1852 | + zn += neutron.pz() / en * dt; |
| 1853 | + } |
| 1854 | + |
| 1855 | + // Velocity of the p-n centre-of-mass frame. |
| 1856 | + const float totalE = ep + en; |
| 1857 | + const float totalPx = proton.px() + neutron.px(); |
| 1858 | + const float totalPy = proton.py() + neutron.py(); |
| 1859 | + const float totalPz = proton.pz() + neutron.pz(); |
| 1860 | + |
| 1861 | + const float bx = totalPx / totalE; |
| 1862 | + const float by = totalPy / totalE; |
| 1863 | + const float bz = totalPz / totalE; |
| 1864 | + |
| 1865 | + const float beta2 = bx * bx + by * by + bz * bz; |
| 1866 | + if (beta2 >= 1.) { |
| 1867 | + continue; |
| 1868 | + } |
| 1869 | + |
| 1870 | + const float gamma = 1. / std::sqrt(1. - beta2); |
| 1871 | + |
| 1872 | + // Boost the proton momentum into the p-n rest frame. |
| 1873 | + // In that frame p_p* = -p_n*, therefore |p_p*| is the relative momentum entering the coalescence cut. |
| 1874 | + float pxStar = proton.px(); |
| 1875 | + float pyStar = proton.py(); |
| 1876 | + float pzStar = proton.pz(); |
| 1877 | + |
| 1878 | + if (beta2 > 0.) { |
| 1879 | + const float betaDotP = bx * proton.px() + by * proton.py() + bz * proton.pz(); |
| 1880 | + |
| 1881 | + const float factor = ((gamma - 1.) * betaDotP / beta2) - gamma * ep; |
| 1882 | + |
| 1883 | + pxStar += factor * bx; |
| 1884 | + pyStar += factor * by; |
| 1885 | + pzStar += factor * bz; |
| 1886 | + } |
| 1887 | + |
| 1888 | + const float pRelative = std::sqrt(pxStar * pxStar + pyStar * pyStar + pzStar * pzStar); |
| 1889 | + |
| 1890 | + if (pRelative >= settingsCoalescence.pMax.value) { |
| 1891 | + continue; |
| 1892 | + } |
| 1893 | + |
| 1894 | + // The two particles have already been propagated to equal time in the lab. Transform their spatial separation to the pair CM frame. |
| 1895 | + float dx = xp - xn; |
| 1896 | + float dy = yp - yn; |
| 1897 | + float dz = zp - zn; |
| 1898 | + |
| 1899 | + if (beta2 > 0.) { |
| 1900 | + const float betaDotR = bx * dx + by * dy + bz * dz; |
| 1901 | + const float factor = (gamma - 1.) * betaDotR / beta2; |
| 1902 | + |
| 1903 | + dx += factor * bx; |
| 1904 | + dy += factor * by; |
| 1905 | + dz += factor * bz; |
| 1906 | + } |
| 1907 | + |
| 1908 | + const float rRelative = std::sqrt(dx * dx + dy * dy + dz * dz); |
| 1909 | + |
| 1910 | + if (rRelative >= settingsCoalescence.rMax.value) { |
| 1911 | + continue; |
| 1912 | + } |
| 1913 | + |
| 1914 | + // Successful coalescence: remove the constituent proton and neutron and replace them by a deuteron carrying their total three-momentum |
| 1915 | + consumed[ip] = true; |
| 1916 | + consumed[in] = true; |
| 1917 | + |
| 1918 | + const float deuteronPt = std::hypot(totalPx, totalPy); |
| 1919 | + const float deuteronPhi = std::atan2(totalPy, totalPx); |
| 1920 | + const float deuteronEta = std::asinh(totalPz / std::max(deuteronPt, 1.e-12f)); |
| 1921 | + |
| 1922 | + deuterons.emplace_back(deuteronPt, |
| 1923 | + deuteronEta, |
| 1924 | + deuteronPhi, |
| 1925 | + deuteronPDG, |
| 1926 | + 0.5f * (xp + xn), |
| 1927 | + 0.5f * (yp + yn), |
| 1928 | + 0.5f * (zp + zn), |
| 1929 | + commonTime, |
| 1930 | + proton.isPhysicalPrimary() && neutron.isPhysicalPrimary()); |
| 1931 | + break; |
| 1932 | + } |
| 1933 | + } |
| 1934 | + } |
| 1935 | + |
| 1936 | + // Construct the post-coalescence particle list. |
| 1937 | + std::vector<GenCoalescenceCandidate> coalescedParticles; |
| 1938 | + coalescedParticles.reserve(particlesToProcess.size() + deuterons.size()); |
| 1939 | + |
| 1940 | + for (size_t i = 0; i < particlesToProcess.size(); ++i) { |
| 1941 | + if (!consumed[i]) { |
| 1942 | + coalescedParticles.push_back(std::move(particlesToProcess[i])); |
| 1943 | + } |
| 1944 | + } |
| 1945 | + |
| 1946 | + for (auto& deuteron : deuterons) { |
| 1947 | + coalescedParticles.push_back(std::move(deuteron)); |
| 1948 | + } |
| 1949 | + |
| 1950 | + particlesToProcess = std::move(coalescedParticles); |
| 1951 | + } |
| 1952 | + |
1698 | 1953 | // Pairing candidates are collected during the QA loop below (which already visits every particle) |
1699 | 1954 | GenCollisionCache genCache; |
1700 | 1955 |
|
1701 | | - for (const auto& particle : mcParticles) { |
| 1956 | + for (const auto& particle : particlesToProcess) { |
1702 | 1957 | auto fillGeneratedQa = [this, &particle](const float binPosition) { |
1703 | 1958 | switch (particle.pdgCode()) { |
1704 | 1959 | case PDG_t::kProton: |
|
0 commit comments