Skip to content

Commit 23b2982

Browse files
authored
Add on-the-fly coal to h-d corr
1 parent 7ec2f86 commit 23b2982

1 file changed

Lines changed: 227 additions & 4 deletions

File tree

‎PWGLF/Tasks/Nuspex/hadronnucleicorrelation.cxx‎

Lines changed: 227 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)