modified: Armory/ClassPW.h

modified:   TrackRecon.C corrected anode cal logic with changes to classpw.h to incorporate the guardwire to cathode wire region for anode charge collection
	modified:   pc_energy_calibration.dat
This commit is contained in:
Vignesh Sitaraman 2026-08-06 13:30:51 -04:00
parent 2d3bcc7c74
commit 00c507e0ac
3 changed files with 184 additions and 161 deletions

View File

@ -616,47 +616,73 @@ inline double PW::GetZ0()
return trackVec.Z(); return trackVec.Z();
} }
// Each wire family is strung as straight skew chords between two rings, so it sweeps a
// one-sheet hyperboloid (x*x+y*y)/(a*a) - (z*z)/(c*c) = 1 whose waist 'a' is smaller than
// the ring radius. The anode waist was fit to the measured anode crossover points; the
// other families share the flare 'c' and scale their waist with the ring radius.
//
// Radial ordering, inner to outer: guard (ring 32) < anode (ring 37) < cathode (ring 43).
// Charge collection spans guard -> cathode. The anodes in the middle are the readout, the
// cathodes see the induced/mirror charge, and the guard wires are field-shaping, not read out.
const double kPCHyperbC = 301.895;
const double kPCAnodeWaist = 32.0429; // ring radius 37 mm -- readout
const double kPCCathodeWaist = 32.0429 * 43.0 / 37.0; // ring radius 43 mm -- induced charge
const double kPCGuardWaist = 32.0429 * 32.0 / 37.0; // ring radius 32 mm -- not read out
// Intersection of the segment x1 -> x1+dx with the one-sheet hyperboloid of waist a, flare c.
// Returns TVector3(0, 0, 54321) when the segment does not cross the surface.
inline TVector3 pc_hyperboloid_intersect(const TVector3 &x1, const TVector3 &dx, double a, double c)
{
double t2 = 1.0; // value of 't' at the destination point, by definition t=(z(t)-z0)/dz
auto A = pow(dx.Perp(), 2) / (a * a) - pow(dx.Z(), 2) / (c * c);
auto B = 2 * (dx.X() * x1.X() + dx.Y() * x1.Y()) / (a * a) - 2 * (dx.Z() * x1.Z()) / (c * c);
auto C = pow(x1.Perp(), 2) / (a * a) - pow(x1.Z(), 2) / (c * c) - 1.0;
double disc = B * B - 4 * A * C;
if (disc < 0)
return TVector3(0, 0, 54321);
double tsol1 = (-B + TMath::Sqrt(disc)) / (2 * A);
double tsol2 = (-B - TMath::Sqrt(disc)) / (2 * A);
if (tsol1 >= 0 && tsol1 <= t2)
return x1 + tsol1 * dx;
else if (tsol2 >= 0 && tsol2 <= t2)
return x1 + tsol2 * dx;
else
return TVector3(0, 0, 54321);
}
inline std::tuple<TVector3, TVector3, double> find_PC_PathLength(const TVector3 &x1, const TVector3 &x2) inline std::tuple<TVector3, TVector3, double> find_PC_PathLength(const TVector3 &x1, const TVector3 &x2)
{ {
/* /*
Function that finds the path length between anode and cathode surfaces, both one-sheet hyperboloids of form (x*x+y*y)/(a*a) - (z*z)/(c*c) = 1 Anode -> cathode segment for a particle moving from x1 to x2.
* path length found for a given particle moving along a certain direction from x1 to x2
* Typical arguments here will be x1=r_rhoMin, x2=qqqevent.pos or sx3event.pos * Typical arguments here will be x1=r_rhoMin, x2=qqqevent.pos or sx3event.pos
* Returns {cathode_intersect, anode_intersect, gap_in_cm}; gap == 54321 sentinel on failure. * Returns {cathode_intersect, anode_intersect, gap_in_cm}; gap == 54321 sentinel on failure.
* NOTE: this is the anode->cathode gap, NOT the full charge-collection region.
For the anode energy calibration use find_PC_CollectionPath() instead.
*/ */
TVector3 dx = x2 - x1; // direction vector TVector3 dx = x2 - x1; // direction vector
double t2 = 1.0; // The value of 't' at the destination point, by definition: t=(z(t)-z0)/dz TVector3 anode_intersect = pc_hyperboloid_intersect(x1, dx, kPCAnodeWaist, kPCHyperbC);
auto onesheet_hyperboloid_intersect = [&](double a, double c) TVector3 cathode_intersect = pc_hyperboloid_intersect(x1, dx, kPCCathodeWaist, kPCHyperbC);
{
auto A = pow(dx.Perp(), 2) / (a * a) - pow(dx.Z(), 2) / (c * c);
auto B = 2 * (dx.X() * x1.X() + dx.Y() * x1.Y()) / (a * a) - 2 * (dx.Z() * x1.Z()) / (c * c);
auto C = pow(x1.Perp(), 2) / (a * a) - pow(x1.Z(), 2) / (c * c) - 1.0;
double disc = B * B - 4 * A * C;
if (disc < 0)
return TVector3(0, 0, 54321);
else
{
double tsol1 = (-B + TMath::Sqrt(disc)) / (2 * A);
double tsol2 = (-B - TMath::Sqrt(disc)) / (2 * A);
if (tsol1 >= 0 && tsol1 <= t2)
return x1 + tsol1 * dx;
else if (tsol2 >= 0 && tsol2 <= t2)
return x1 + tsol2 * dx;
else
return TVector3(0, 0, 54321);
}
};
// TODO: Magic numbers here describing waist 'a', and flare 'c' will need better treatment.
// Currently, these are derived by fitting the crossover points to R^2/a^2 - z^2/c^2 = 1 for anodes
// Cathode a, c values are found by scaling up the anode waist by 43/37, the ratio of the outermost radii
TVector3 anode_intersect = onesheet_hyperboloid_intersect(32.0429, 301.895);
TVector3 cathode_intersect = onesheet_hyperboloid_intersect(37.239045, 301.895);
if (anode_intersect.Z() != 54321 && cathode_intersect.Z() != 54321) if (anode_intersect.Z() != 54321 && cathode_intersect.Z() != 54321)
return std::tuple(cathode_intersect, anode_intersect, (cathode_intersect - anode_intersect).Mag() * 0.1); return std::tuple(cathode_intersect, anode_intersect, (cathode_intersect - anode_intersect).Mag() * 0.1);
else else
return std::tuple(TVector3(0, 0, 0), TVector3(0, 0, 0), 54321); return std::tuple(TVector3(0, 0, 0), TVector3(0, 0, 0), 54321);
} }
inline std::tuple<TVector3, TVector3, double> find_PC_CollectionPath(const TVector3 &x1, const TVector3 &x2)
{
/*
Charge-collection region for a particle moving from x1 to x2: it begins where the track
passes the guard wires and ends at the cathode. This is the segment whose energy deposit
the anodes actually collect, so it is the dE target for the anode energy calibration.
* Returns {guard_intersect, cathode_intersect, thickness_in_cm}; 54321 sentinel on failure.
*/
TVector3 dx = x2 - x1;
TVector3 guard_intersect = pc_hyperboloid_intersect(x1, dx, kPCGuardWaist, kPCHyperbC);
TVector3 cathode_intersect = pc_hyperboloid_intersect(x1, dx, kPCCathodeWaist, kPCHyperbC);
if (guard_intersect.Z() != 54321 && cathode_intersect.Z() != 54321)
return std::tuple(guard_intersect, cathode_intersect, (cathode_intersect - guard_intersect).Mag() * 0.1);
else
return std::tuple(TVector3(0, 0, 0), TVector3(0, 0, 0), 54321);
}
#endif #endif

View File

@ -114,6 +114,25 @@ inline PCPath pcPath(const TVector3 &vtx, const TVector3 &si)
return {true, dl, a, a - dl}; return {true, dl, a, a - dl};
} }
// Charge-collection region, guard wires -> cathode. The track crosses these outward-bound
// (vtx -> guard -> anode -> cathode -> si), so measured back from the Si end the guard is
// the farther surface: guard_cm > cathode_cm and thick_cm = guard_cm - cathode_cm.
struct PCCollect
{
bool ok;
double thick_cm; // guard -> cathode, cm
double guard_cm; // Si -> guard surface
double cathode_cm; // Si -> cathode surface
};
inline PCCollect pcCollectionPath(const TVector3 &vtx, const TVector3 &si)
{
auto [gint, cint, dl] = find_PC_CollectionPath(vtx, si);
if (dl >= 54321.0)
return {false, 0.0, 0.0, 0.0};
double g = (si - gint).Mag() * 0.1;
return {true, dl, g, g - dl};
}
double ejectile_z_deut = -220.0; // vertex_z below this -> deuteron double ejectile_z_deut = -220.0; // vertex_z below this -> deuteron
double ejectile_e_alpha = 2000.0; // PC anode energy above this -> alpha double ejectile_e_alpha = 2000.0; // PC anode energy above this -> alpha
// Per-ejectile (ejectile mass, recoil mass) for a reaction's (a,a)/(a,d)/(a,p) // Per-ejectile (ejectile mass, recoil mass) for a reaction's (a,a)/(a,d)/(a,p)
@ -793,7 +812,10 @@ inline double evalElossForward(TSpline3 *fwd, TSpline3 *inv, double E, double pa
return 0.0; // extrapolated past the tabulated stopping point -> treat as fully stopped return 0.0; // extrapolated past the tabulated stopping point -> treat as fully stopped
return e; return e;
} }
inline void pcEnergyCalibrationAccumulate(const std::vector<Event> &PC_Events, const std::vector<Event> &QQQ_Events, const std::vector<Event> &SX3_Events)
inline void pcEnergyCalibrationAccumulate(const std::vector<Event> &PC_Events,
const std::vector<Event> &QQQ_Events,
const std::vector<Event> &SX3_Events)
{ {
const TVector3 source_pos(beam_axis_x, beam_axis_y, source_vertex); const TVector3 source_pos(beam_axis_x, beam_axis_y, source_vertex);
for (const auto &pcevent : PC_Events) for (const auto &pcevent : PC_Events)
@ -811,32 +833,32 @@ inline void pcEnergyCalibrationAccumulate(const std::vector<Event> &PC_Events, c
interaction.SetZ(pcz); interaction.SetZ(pcz);
} }
// Extend track to find anode/cathode surface crossings (FIX 1: separate targets). // Extend the source->interaction track to find where it enters the collection region
// (guard wires) and where it leaves it (cathode).
TVector3 trackVec = interaction - source_pos; TVector3 trackVec = interaction - source_pos;
if (trackVec.Mag() < 0.01) if (trackVec.Mag() < 0.01)
continue; continue; // degenerate -- source and hit coincide
TVector3 farPoint = source_pos + 2000.0 * trackVec.Unit(); TVector3 farPoint = source_pos + 2000.0 * trackVec.Unit(); // well beyond Si at ~88 mm
auto [cint_s, aint_s, dl_s] = find_PC_PathLength(source_pos, farPoint); auto [gint_s, cint_s, dl_s] = find_PC_CollectionPath(source_pos, farPoint);
if (dl_s >= 54321.0) if (dl_s >= 54321.0)
continue; // geometry intersection failed
double dist_to_entry = (gint_s - source_pos).Mag() * 0.1; // source -> guard wires, cm
double dist_to_exit = (cint_s - source_pos).Mag() * 0.1; // source -> cathode, cm
if (!std::isfinite(dist_to_entry) || dist_to_entry <= 0.0 ||
!std::isfinite(dist_to_exit) || dist_to_exit <= 0.0 ||
dist_to_entry >= dist_to_exit)
continue; continue;
double dist_to_anode = (aint_s - source_pos).Mag() * 0.1; double E_entry = evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, pc_calib_alpha_source_mev, dist_to_entry);
double dist_to_cathode = (cint_s - source_pos).Mag() * 0.1; double E_exit = evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, pc_calib_alpha_source_mev, dist_to_exit);
if (!std::isfinite(dist_to_anode) || dist_to_anode <= 0.0 || if (!std::isfinite(E_entry) || E_entry <= 0.0 ||
!std::isfinite(dist_to_cathode) || dist_to_cathode <= 0.0) !std::isfinite(E_exit) || E_exit < 0.0 || E_entry <= E_exit)
continue;
double dE_anode = pc_calib_alpha_source_mev - evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl,
pc_calib_alpha_source_mev, dist_to_anode);
double dE_cathode = pc_calib_alpha_source_mev - evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl,
pc_calib_alpha_source_mev, dist_to_cathode);
if (!std::isfinite(dE_anode) || dE_anode <= 0.0 ||
!std::isfinite(dE_cathode) || dE_cathode <= 0.0)
continue; continue;
if (pcevent.Anodech >= 0 && pcevent.Anodech < 24) if (pcevent.Anodech >= 0 && pcevent.Anodech < 24)
pcCalibData[pcevent.Anodech].push_back({pcevent.Energy1, dE_anode}); pcCalibData[pcevent.Anodech].push_back({pcevent.Energy1, E_entry - E_exit});
if (pcevent.Cathodech >= 0 && pcevent.Cathodech < 24)
pcCalibData[24 + pcevent.Cathodech].push_back({pcevent.Energy2, dE_cathode});
// Si-coincidence supplement: z-dependent anode radius (FIX 2) + separate dE (FIX 1). // Si-coincidence supplement: for each matching Si event, project the pcz using the
// z-dependent anode radius (FIX 2: z_to_crossover_rho, not a flat 37 mm) as a sanity
// gate, then take the collection-region dE via pcCollectionPath(source, si).
auto considerSi = [&](const Event &sievent, double phi_win) auto considerSi = [&](const Event &sievent, double phi_win)
{ {
if (TMath::Abs(sievent.pos.DeltaPhi(pcevent.pos)) > phi_win) if (TMath::Abs(sievent.pos.DeltaPhi(pcevent.pos)) > phi_win)
@ -844,27 +866,28 @@ inline void pcEnergyCalibrationAccumulate(const std::vector<Event> &PC_Events, c
double theta = (sievent.pos - source_pos).Theta(); double theta = (sievent.pos - source_pos).Theta();
if (theta <= 0.0 || !std::isfinite(theta)) if (theta <= 0.0 || !std::isfinite(theta))
return; return;
// Use z-dependent anode crossover radius for the projected pcz validity check.
double z = z_to_crossover_rho(pcevent.pos.Z()) / TMath::Tan(theta) + source_vertex; double z = z_to_crossover_rho(pcevent.pos.Z()) / TMath::Tan(theta) + source_vertex;
if (!std::isfinite(z) || TMath::Abs(z) > 200) if (!std::isfinite(z) || TMath::Abs(z) > 200)
return; return;
PCPath pp = pcPath(source_pos, sievent.pos); // pcCollectionPath: guard_cm = si->guard, cathode_cm = si->cathode (both from the si end).
if (!pp.ok) // Crossing order from the beam axis: source -> guard -> cathode -> si, so measured from
// the source, dist_to_entry = total - guard_cm < dist_to_exit = total - cathode_cm.
PCCollect pc = pcCollectionPath(source_pos, sievent.pos);
if (!pc.ok)
return; return;
double tot = pathLengthCm(source_pos, sievent.pos); double tot = pathLengthCm(source_pos, sievent.pos);
double d_an = tot - pp.anode_cm; double d_en = tot - pc.guard_cm;
double d_ca = tot - pp.cathode_cm; double d_ex = tot - pc.cathode_cm;
if (!std::isfinite(d_an) || d_an <= 0.0 || !std::isfinite(d_ca) || d_ca <= 0.0) if (!std::isfinite(d_en) || d_en <= 0.0 || !std::isfinite(d_ex) || d_ex <= 0.0 ||
d_en >= d_ex)
return; return;
double dEan = pc_calib_alpha_source_mev - evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, double Ee = evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, pc_calib_alpha_source_mev, d_en);
pc_calib_alpha_source_mev, d_an); double Ex = evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, pc_calib_alpha_source_mev, d_ex);
double dEca = pc_calib_alpha_source_mev - evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, if (!std::isfinite(Ee) || Ee <= 0.0 || !std::isfinite(Ex) || Ex < 0.0 || Ee <= Ex)
pc_calib_alpha_source_mev, d_ca);
if (!std::isfinite(dEan) || dEan <= 0.0 || !std::isfinite(dEca) || dEca <= 0.0)
return; return;
if (pcevent.Anodech >= 0 && pcevent.Anodech < 24) if (pcevent.Anodech >= 0 && pcevent.Anodech < 24)
pcCalibData[pcevent.Anodech].push_back({pcevent.Energy1, dEan}); pcCalibData[pcevent.Anodech].push_back({pcevent.Energy1, Ee - Ex});
if (pcevent.Cathodech >= 0 && pcevent.Cathodech < 24)
pcCalibData[24 + pcevent.Cathodech].push_back({pcevent.Energy2, dEca});
}; };
for (const auto &qqqevent : QQQ_Events) for (const auto &qqqevent : QQQ_Events)
considerSi(qqqevent, TMath::Pi() / 4.0); considerSi(qqqevent, TMath::Pi() / 4.0);
@ -990,24 +1013,26 @@ inline void pcEnergyCalibrationAccumulateProton(const std::vector<Event> &PC_Eve
if (predicted_alpha_E <= 0.0) if (predicted_alpha_E <= 0.0)
return; return;
PCPath pp = pcPath(vertex, sievent.pos); // pcCollectionPath: guard_cm = si->guard, cathode_cm = si->cathode (both from the si end).
if (!pp.ok) // Crossing order from the beam axis: vertex -> guard -> cathode -> si, so measured from
// the vertex, dist_to_entry = total - guard_cm < dist_to_exit = total - cathode_cm.
PCCollect pc = pcCollectionPath(vertex, sievent.pos);
if (!pc.ok)
return; return;
double total_cm = pathLengthCm(vertex, sievent.pos); double total_cm = pathLengthCm(vertex, sievent.pos);
double dist_to_anode = total_cm - pp.anode_cm; double dist_to_entry = total_cm - pc.guard_cm; // vertex -> guard wires, cm
double dist_to_cathode = total_cm - pp.cathode_cm; double dist_to_exit = total_cm - pc.cathode_cm; // vertex -> cathode, cm
if (!std::isfinite(dist_to_anode) || dist_to_anode <= 0.0 || if (!std::isfinite(dist_to_entry) || dist_to_entry <= 0.0 ||
!std::isfinite(dist_to_cathode) || dist_to_cathode <= 0.0) !std::isfinite(dist_to_exit) || dist_to_exit <= 0.0 ||
dist_to_entry >= dist_to_exit)
return; return;
double dE_anode = predicted_alpha_E - evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, predicted_alpha_E, dist_to_anode); double E_entry = evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, predicted_alpha_E, dist_to_entry);
double dE_cathode = predicted_alpha_E - evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, predicted_alpha_E, dist_to_cathode); double E_exit = evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, predicted_alpha_E, dist_to_exit);
if (!std::isfinite(dE_anode) || dE_anode <= 0.0 || if (!std::isfinite(E_entry) || E_entry <= 0.0 ||
!std::isfinite(dE_cathode) || dE_cathode <= 0.0) !std::isfinite(E_exit) || E_exit < 0.0 || E_entry <= E_exit)
return; return;
if (pcevent.multi1 == 1 && pcevent.Anodech >= 0 && pcevent.Anodech < 24) if (pcevent.multi1 == 1 && pcevent.Anodech >= 0 && pcevent.Anodech < 24)
pcCalibData[pcevent.Anodech].push_back({pcevent.Energy1, dE_anode}); pcCalibData[pcevent.Anodech].push_back({pcevent.Energy1, E_entry - E_exit});
if (pcevent.Cathodech >= 0 && pcevent.Cathodech < 24)
pcCalibData[24 + pcevent.Cathodech].push_back({pcevent.Energy2, dE_cathode});
}; };
for (const auto &pcevent : PC_Events) for (const auto &pcevent : PC_Events)
@ -1595,44 +1620,17 @@ Bool_t TrackRecon::Process(Long64_t entry)
{ {
Event PCEventCalibrated = PCEvent; Event PCEventCalibrated = PCEvent;
double anodeCalibSum = 0.0; double anodeCalibSum = 0.0;
double calibWire0 = 0.0, calibWire1 = 0.0; // per-wire slope*ADC (no intercept), for the A2 ratio below for (const auto &w : aCluster)
int primaryAnodeWire = -1;
double primaryAnodeE = -1.0;
for (size_t wi_i = 0; wi_i < aCluster.size(); ++wi_i)
{ {
const auto &w = aCluster[wi_i];
int wi = std::get<0>(w); int wi = std::get<0>(w);
double wRawE = std::get<1>(w); if (wi >= 0 && wi < 24)
double wCalibE = (wi >= 0 && wi < 24) ? pcEnergySlope[wi] * wRawE : 0.0; anodeCalibSum += pcEnergySlope[wi] * std::get<1>(w) + pcEnergyIntercept[wi];
anodeCalibSum += wCalibE;
if (wi_i == 0)
calibWire0 = wCalibE;
else if (wi_i == 1)
calibWire1 = wCalibE;
if (wRawE > primaryAnodeE)
{
primaryAnodeE = wRawE;
primaryAnodeWire = wi;
}
} }
double primaryIntercept = (primaryAnodeWire >= 0 && primaryAnodeWire < 24) ? pcEnergyIntercept[primaryAnodeWire] : 0.0; PCEventCalibrated.Energy1 = anodeCalibSum;
PCEventCalibrated.Energy1 = anodeCalibSum + primaryIntercept;
// Cathode uses the single max wire (cpMaxE) -- indexed by z, so it's // Cathode uses the single max wire (cpMaxE) -- indexed by z, so it's
// already phi-consistent; leave it as-is. // already phi-consistent; leave it as-is.
PCEventCalibrated.Energy2 = pcEnergySlope[24 + PCEvent.Cathodech] * cpMaxE + pcEnergyIntercept[24 + PCEvent.Cathodech]; PCEventCalibrated.Energy2 = pcEnergySlope[24 + PCEvent.Cathodech] * cpMaxE + pcEnergyIntercept[24 + PCEvent.Cathodech];
PC_Events_calibrated.push_back(PCEventCalibrated); PC_Events_calibrated.push_back(PCEventCalibrated);
if (aCluster.size() == 2)
{
double eSmaller = std::min(calibWire0, calibWire1);
double eLarger = std::max(calibWire0, calibWire1);
double ratio = (eLarger > 0.0) ? (eSmaller / eLarger) : 0.0;
plotter->Fill1D("Calib_A2_AnodeRatio", 200, 0.0, 1.0, ratio, "hCalibPC");
plotter->Fill2D("Calib_A2_AnodeRatio_vs_Phi", 360, -180, 180, 200, 0.0, 1.0,
PCEvent.pos.Phi() * 180.0 / M_PI, ratio, "hCalibPC");
plotter->Fill2D("Calib_A2_AnodeRatio_vs_TotalE", 400, 0, 10, 200, 0.0, 1.0,
PCEventCalibrated.Energy1, ratio, "hCalibPC");
}
} }
} }
else else
@ -1687,22 +1685,12 @@ Bool_t TrackRecon::Process(Long64_t entry)
if (pcEnergyCalibLoaded) if (pcEnergyCalibLoaded)
{ {
double anodeCalibSum = 0.0; double anodeCalibSum = 0.0;
int primaryAnodeWireA1C0 = -1;
double primaryAnodeEA1C0 = -1.0;
for (const auto &w : aCl) for (const auto &w : aCl)
{ {
int wi = std::get<0>(w); int wi = std::get<0>(w);
double wRawE = std::get<1>(w);
if (wi >= 0 && wi < 24) if (wi >= 0 && wi < 24)
anodeCalibSum += pcEnergySlope[wi] * wRawE; anodeCalibSum += pcEnergySlope[wi] * std::get<1>(w) + pcEnergyIntercept[wi];
if (wRawE > primaryAnodeEA1C0)
{
primaryAnodeEA1C0 = wRawE;
primaryAnodeWireA1C0 = wi;
}
} }
double primaryInterceptA1C0 = (primaryAnodeWireA1C0 >= 0 && primaryAnodeWireA1C0 < 24) ? pcEnergyIntercept[primaryAnodeWireA1C0] : 0.0;
anodeCalibSum += primaryInterceptA1C0;
Event ev(pc, anodeCalibSum, -1.0, apTSMaxE, -1.0); Event ev(pc, anodeCalibSum, -1.0, apTSMaxE, -1.0);
ev.multi1 = static_cast<int>(aCl.size()); ev.multi1 = static_cast<int>(aCl.size());
ev.multi2 = 0; // no cathode -> a{n}c0 topology in pcCalibratedHistograms ev.multi2 = 0; // no cathode -> a{n}c0 topology in pcCalibratedHistograms
@ -1711,21 +1699,34 @@ Bool_t TrackRecon::Process(Long64_t entry)
PC_Events_calibrated.push_back(ev); PC_Events_calibrated.push_back(ev);
} }
if (doPCEnergyCalibration && source_run) // Anode-wire calibration point -- source runs only. The fixed alpha-source
// energy is only valid there; proton-run A1C0 has no elastic tag to predict
// its energy, so it contributes to the display but not the fit.
if (doPCEnergyCalibration && !ta_foil_run)
{ {
double path = pathLengthCm(source_pos_a1c0, pc); TVector3 ray_dir = (pc - source_pos_a1c0).Unit();
double e_rem = evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, pc_calib_alpha_source_mev, path); TVector3 virt_out = source_pos_a1c0 + ray_dir * 120.0;
double dE_gas = pc_calib_alpha_source_mev - e_rem; PCCollect pcc = pcCollectionPath(source_pos_a1c0, virt_out);
if (std::isfinite(dE_gas) && dE_gas > 0.0) if (pcc.ok)
pcCalibData[anodeIdx].push_back({apSumE, dE_gas}); {
double tc = pathLengthCm(source_pos_a1c0, virt_out);
double de = tc - pcc.guard_cm; // source -> guard wires, cm
double dx = tc - pcc.cathode_cm; // source -> cathode, cm
if (std::isfinite(de) && de > 0.0 && std::isfinite(dx) && dx > de)
{
double Ee = evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, pc_calib_alpha_source_mev, de);
double Ex = evalElossForward(MeV_to_cm_spl, cm_to_MeV_spl, pc_calib_alpha_source_mev, dx);
if (std::isfinite(Ee) && std::isfinite(Ex) && Ee > Ex && Ex >= 0.0)
pcCalibData[anodeIdx].push_back({apSumE, Ee - Ex});
}
}
} }
} }
} }
if (doPCEnergyCalibration) if (doPCEnergyCalibration)
{ {
if (source_run) pcEnergyCalibrationAccumulate(PC_Events, QQQ_Events, SX3_Events);
pcEnergyCalibrationAccumulate(PC_Events, SX3_Events, QQQ_Events);
pcEnergyCalibrationAccumulateProton(PC_Events, QQQ_Events, SX3_Events); pcEnergyCalibrationAccumulateProton(PC_Events, QQQ_Events, SX3_Events);
} }
@ -1934,7 +1935,9 @@ void TrackRecon::Terminate()
} }
} }
outfile.close(); outfile.close();
std::cout << "PC energy calibration: appended " << nPoints << " raw points to " << outname << std::endl; std::cout << "PC energy calibration: appended " << nPoints << " raw points to " << outname
<< " -- run pccal/fit_pc_energy_calibration.C once all calibration runs are done"
<< " to (re)produce pc_energy_calibration.dat" << std::endl;
} }
} }
@ -2051,16 +2054,12 @@ void pcCalibratedHistograms(HistPlotter *plotter, const std::vector<Event> &QQQ_
{ {
const std::string topo = "_a" + std::to_string(pcevent.multi1) + "c" + std::to_string(pcevent.multi2); const std::string topo = "_a" + std::to_string(pcevent.multi1) + "c" + std::to_string(pcevent.multi2);
const bool hasCathode = (pcevent.Cathodech >= 0); const bool hasCathode = (pcevent.Cathodech >= 0);
const double totalE = hasCathode ? (pcevent.Energy1 + pcevent.Energy2) : pcevent.Energy1; if (hasCathode)
const double dE = hasCathode ? (pcevent.Energy1 - pcevent.Energy2) : pcevent.Energy1;
if (hasCathode)
plotter->Fill2D("Calib_AnodeE_vs_CathodeE_a1c1andup", 800, 0, 3, 800, 0, 3, pcevent.Energy1, pcevent.Energy2, "hCalibPC"); plotter->Fill2D("Calib_AnodeE_vs_CathodeE_a1c1andup", 800, 0, 3, 800, 0, 3, pcevent.Energy1, pcevent.Energy2, "hCalibPC");
for (const std::string &t : {std::string(""), topo}) for (const std::string &t : {std::string(""), topo})
{ {
plotter->Fill2D("Calib_AnodeE_vs_AnodeIndex" + t, 24, 0, 24, 800, 0, 3, pcevent.Anodech, pcevent.Energy1, "hCalibPC"); plotter->Fill2D("Calib_AnodeE_vs_AnodeIndex" + t, 24, 0, 24, 800, 0, 3, pcevent.Anodech, pcevent.Energy1, "hCalibPC");
plotter->Fill1D("Calib_AnodeE" + t, 800, 0, 3, pcevent.Energy1, "hCalibPC"); plotter->Fill1D("Calib_AnodeE" + t, 800, 0, 3, pcevent.Energy1, "hCalibPC");
plotter->Fill2D("Calib_dE_vs_Z" + t, 400, -200, 200, 800, 0, 3, pcevent.pos.Z(), dE, "hCalibPC");
plotter->Fill2D("Calib_dE_vs_Phi" + t, 360, -180, 180, 800, 0, 3, pcevent.pos.Phi() * 180 / M_PI, dE, "hCalibPC");
if (hasCathode) if (hasCathode)
{ {
plotter->Fill2D("Calib_CathodeE_vs_CathodeIndex" + t, 24, 0, 24, 800, 0, 3, pcevent.Cathodech, pcevent.Energy2, "hCalibPC"); plotter->Fill2D("Calib_CathodeE_vs_CathodeIndex" + t, 24, 0, 24, 800, 0, 3, pcevent.Cathodech, pcevent.Energy2, "hCalibPC");
@ -2071,14 +2070,12 @@ void pcCalibratedHistograms(HistPlotter *plotter, const std::vector<Event> &QQQ_
for (const auto &qqqevent : QQQ_Events) for (const auto &qqqevent : QQQ_Events)
{ {
plotter->Fill2D("Calib_dE_AnodeE_vs_QQQE" + t, 400, 0, 10, 800, 0, 3, qqqevent.Energy1, pcevent.Energy1, "hCalibPC"); plotter->Fill2D("Calib_dE_AnodeE_vs_QQQE" + t, 400, 0, 10, 800, 0, 3, qqqevent.Energy1, pcevent.Energy1, "hCalibPC");
plotter->Fill2D("Calib_dE_dE_vs_QQQE" + t, 400, 0, 10, 800, 0, 3, qqqevent.Energy1, dE, "hCalibPC"); if (hasCathode)
if (hasCathode)
plotter->Fill2D("Calib_dE_CathodeE_vs_QQQE" + t, 400, 0, 10, 800, 0, 3, qqqevent.Energy1, pcevent.Energy2, "hCalibPC"); plotter->Fill2D("Calib_dE_CathodeE_vs_QQQE" + t, 400, 0, 10, 800, 0, 3, qqqevent.Energy1, pcevent.Energy2, "hCalibPC");
} }
for (const auto &sx3event : SX3_Events) for (const auto &sx3event : SX3_Events)
{ {
plotter->Fill2D("Calib_dE_AnodeE_vs_SX3E" + t, 400, 0, 10, 800, 0, 3, sx3event.Energy1, pcevent.Energy1, "hCalibPC"); plotter->Fill2D("Calib_dE_AnodeE_vs_SX3E" + t, 400, 0, 10, 800, 0, 3, sx3event.Energy1, pcevent.Energy1, "hCalibPC");
plotter->Fill2D("Calib_dE_dE_vs_SX3E" + t, 400, 0, 10, 800, 0, 3, sx3event.Energy1, dE, "hCalibPC");
if (hasCathode) if (hasCathode)
plotter->Fill2D("Calib_dE_CathodeE_vs_SX3E" + t, 400, 0, 10, 800, 0, 3, sx3event.Energy1, pcevent.Energy2, "hCalibPC"); plotter->Fill2D("Calib_dE_CathodeE_vs_SX3E" + t, 400, 0, 10, 800, 0, 3, sx3event.Energy1, pcevent.Energy2, "hCalibPC");
} }
@ -4027,9 +4024,9 @@ static void reaction_ax_core(HistPlotter *plotter, const std::vector<Event> &Si_
plotter->Fill2D(rx + "_dEgas_vs_Ef" + ejtag + sfx, 400, 0, ef_max, 400, 0, 1, Efix, E_an - E_ca, pmlabel); plotter->Fill2D(rx + "_dEgas_vs_Ef" + ejtag + sfx, 400, 0, ef_max, 400, 0, 1, Efix, E_an - E_ca, pmlabel);
if (anodeE_MeV >= 0.0 && cathodeE_MeV >= 0.0) if (anodeE_MeV >= 0.0 && cathodeE_MeV >= 0.0)
{ {
plotter->Fill2D(rx + "_dEgasCalib_vs_Ef" + ejtag + sfx, 400, 0, ef_max, 800, -2, 2, Efix, anodeE_MeV - cathodeE_MeV, pmlabel); plotter->Fill2D(rx + "_dEgasCalib_vs_Ef" + ejtag + sfx, 400, 0, ef_max, 800, -2, 2, Efix, anodeE_MeV , pmlabel);
plotter->Fill2D(rx + "_dEgasCalib_vs_E" + ejtag + sfx, 400, 0, ef_max, 800, -2, 2, sievent.Energy1, anodeE_MeV - cathodeE_MeV, pmlabel); plotter->Fill2D(rx + "_dEgasCalib_vs_E" + ejtag + sfx, 400, 0, ef_max, 800, -2, 2, sievent.Energy1, anodeE_MeV , pmlabel);
plotter->Fill2D(rx + "_dEgasPred_vs_dEgasCalib" + ejtag + sfx, 800, -2, 2, 400, 0, 2, anodeE_MeV - cathodeE_MeV, E_an - E_ca, pmlabel); plotter->Fill2D(rx + "_dEgasPred_vs_dEgasCalib" + ejtag + sfx, 800, -2, 2, 400, 0, 2, anodeE_MeV , E_an - E_ca, pmlabel);
} }
} }
}; };

View File

@ -1,27 +1,27 @@
0 8.024604e-05 0.000000e+00 0 1.181280e-04 0.000000e+00
1 7.374085e-05 0.000000e+00 1 7.019636e-05 0.000000e+00
2 9.583027e-05 0.000000e+00 2 7.051555e-05 0.000000e+00
3 9.008263e-05 0.000000e+00 3 7.969780e-05 0.000000e+00
4 8.452456e-05 0.000000e+00 4 9.630606e-05 0.000000e+00
5 1.835247e-04 0.000000e+00 5 1.113250e-04 0.000000e+00
6 5.125204e-05 0.000000e+00 6 6.266591e-05 0.000000e+00
7 1.081530e-04 0.000000e+00 7 7.560064e-05 0.000000e+00
8 1.269708e-04 0.000000e+00 8 7.904770e-05 0.000000e+00
9 2.018195e-04 0.000000e+00 9 1.866785e-05 0.000000e+00
10 1.089829e-04 0.000000e+00 10 5.270549e-05 0.000000e+00
11 8.956875e-05 0.000000e+00 11 5.115143e-05 0.000000e+00
12 1.000000e+00 0.000000e+00 12 3.754198e-05 0.000000e+00
13 7.299374e-05 0.000000e+00 13 3.747917e-05 0.000000e+00
14 7.270788e-05 0.000000e+00 14 3.904528e-05 0.000000e+00
15 8.406974e-05 0.000000e+00 15 3.785797e-05 0.000000e+00
16 8.997978e-05 0.000000e+00 16 3.558300e-05 0.000000e+00
17 4.542671e-05 0.000000e+00 17 4.164502e-05 0.000000e+00
18 4.755692e-05 0.000000e+00 18 5.033098e-05 0.000000e+00
19 2.138104e-04 0.000000e+00 19 1.346289e-04 0.000000e+00
20 9.264217e-05 0.000000e+00 20 4.098874e-05 0.000000e+00
21 1.701897e-04 0.000000e+00 21 3.182742e-05 0.000000e+00
22 2.162763e-04 0.000000e+00 22 2.725084e-05 0.000000e+00
23 3.219591e-04 0.000000e+00 23 3.143861e-05 0.000000e+00
24 7.364520e-05 0.000000e+00 24 7.364520e-05 0.000000e+00
25 9.343009e-05 0.000000e+00 25 9.343009e-05 0.000000e+00
26 1.013807e-04 0.000000e+00 26 1.013807e-04 0.000000e+00