new file: Armory/PCZRecon.h refactor to lift a1c0 and a1c1 frac into a class PCZRecon which includes the a1c2 slope correction as well. Making the code more readable and easier to maintain.

also changes the model_invert into a function call instead of using the TF1 class
	modified:   TrackRecon.C refactor plus removal of older vesions of pcz and track reconstruction from the old analysis. Changed the ranges for dECalib spectra since their max was way too high.
	modified:   pc_energy_calibration.dat changed the calibration of anode 2-4 to the same as anode1 and 5 to 6 becuase they were causing bifuraction in signal. This might have been happening because the 17F data is poisoning the calibraion. This hypothesis
    is pending validation however.
This commit is contained in:
Vignesh Sitaraman 2026-08-12 15:02:35 -04:00
parent fbd601ec40
commit 98c48af0b1
3 changed files with 473 additions and 511 deletions

297
Armory/PCZRecon.h Normal file
View File

@ -0,0 +1,297 @@
#ifndef PCZRecon_h
#define PCZRecon_h
// PC Z-position reconstruction, one section per anode/cathode topology:
// A1C0 (anode only), A1C1 (anode + single cathode, charge division), A1C2
// (anode + two cathodes, "step ladder" correction). Each topology gets one
// well-defined entry point instead of the math being split across files by
// historical accident (A1C0/A1C1 used to live in TrackRecon.C itself; A1C2's
// underlying model lives in the separately-shared PC_StepLadder_Correction.h
// -- see the A1C2 section below for why that one isn't just moved in).
//
// This header holds the reconstruction MATH only. The per-dataset tuning
// constants it reads (cfrac fit parameters, dead-wire lists, Z calibration,
// beam-axis origin) are still owned and set by TrackRecon.C's Begin() --
// this header just declares them `extern` so the same single translation
// unit (TrackRecon.C is compiled as one .C file via ACLiC) can see them.
// Moving the constants themselves out is a separate, riskier change and is
// deliberately NOT done here.
//
// Relocated verbatim from TrackRecon.C (no logic changes): A1C1CellSol,
// A1C1Sol, solve_cell, a1c1_solve, SideChoice, a1c1_pick_side, a1c1_zcorr,
// a1c0_hybrid_pcz (split into a1c0_wirePos + a1c0_hybrid_pcz).
//
// New in this header (see call-site migration notes where each is used):
// a1c1_solve_pick, a1c1_cfrac_pcz, a1c2_zfix.
#include <TVector3.h>
#include <TRandom3.h>
#include <TMath.h>
#include "ClassPW.h"
// --- Per-dataset tuning constants, defined and set in TrackRecon.C ---
extern PW pwinstance;
extern const double a1c1_zg[8];
extern double a1c1_cfmin_cell[7];
extern double a1c1_k_cell[7];
extern double a1c1_cfmin2_cell[7];
extern double a1c1_k2_cell[7];
extern double a1c1_cfrac_split;
extern double a1c1_lowband_rfactor;
extern double a1c1_missing_fmax;
extern double a1c1_z_scale_qqq;
extern double a1c1_z_off_qqq;
extern double a1c1_z_scale_sx3;
extern double a1c1_z_off_sx3;
bool a1c1_missing_neighbor(int awire, int cwire); // TrackRecon.C: dead-wire-adjacency check
TVector3 beamVertex(const TVector3 &si, const TVector3 &dir);
double beamPerp(const TVector3 &p);
// ---------------------------------------------------------------------
// A1C0: single-wire (anode only) position reconstruction
// ---------------------------------------------------------------------
// Anode-wire Z, corrected by the a1c1-derived scale+offset (a1c1_zcorr),
// same reference frame the a1c1 solve below reports in.
inline double a1c1_zcorr(double z_a1c0, bool isQQQ)
{
double scale = isQQQ ? a1c1_z_scale_qqq : a1c1_z_scale_sx3;
double off = isQQQ ? a1c1_z_off_qqq : a1c1_z_off_sx3;
return z_a1c0 * (1.0 - scale) - off;
}
// The raw (undithered) A1C0 wire position: nearest-wire XY at the given
// track phi, Z corrected into the same frame a1c1 uses. This is what
// _rawZ_a1c0 / phi cuts / _dPhi_a1c0-style diagnostics should read --
// anywhere you want the true discretized wire position, not a smoothed one.
inline TVector3 a1c0_wirePos(const std::pair<TVector3, TVector3> &apwire, double phi, bool isQQQ)
{
TVector3 pc = pwinstance.getClosestWirePosAtWirePhi(apwire, phi);
pc.SetZ(a1c1_zcorr(pc.Z(), isQQQ));
return pc;
}
// a1c0_wirePos, then Gaussian-dithered in Z to hide wire-pitch quantization.
// sigma is the caller's choice (dither_sigma or dither_sigma_c0/2.0 etc in
// TrackRecon.C) -- this function doesn't know which convention is "correct"
// for a given call site, only how to apply whichever sigma it's given.
inline TVector3 a1c0_hybrid_pcz(const std::pair<TVector3, TVector3> &apwire, double phi,
bool isQQQ, double sigma, TRandom3 &rand)
{
TVector3 pc = a1c0_wirePos(apwire, phi, isQQQ);
pc.SetZ(rand.Gaus(pc.Z(), sigma));
return pc;
}
// ---------------------------------------------------------------------
// A1C1: single-anode + single-cathode charge-division position reconstruction
// ---------------------------------------------------------------------
struct A1C1CellSol
{
int cell = -1;
double pcz = -99999;
double f = 0.0;
double pitch = 0.0;
bool inband = false;
bool pitchok = false;
};
struct A1C1Sol
{
int band;
double cfrac_used;
double pcz_lo;
double pcz_hi;
A1C1CellSol hi;
A1C1CellSol lo;
};
inline A1C1CellSol solve_cell(int cell, int wf, double zf, double cfrac,
const double *cfmin, const double *kk, bool dead_neighbor)
{
A1C1CellSol s;
s.cell = cell;
s.pcz = zf; // safe sentinel: fired-wire position so edge-wire defaults don't read as z=0
if (cell < 0 || cell > 6)
return s;
double zc = 0.5 * (a1c1_zg[cell] + a1c1_zg[cell + 1]); // cell centre
double half = 0.5 * (a1c1_zg[cell] - a1c1_zg[cell + 1]); // half-cell width
double pitch = a1c1_zg[cell] - a1c1_zg[cell + 1]; // full wire spacing
if (half <= 0.0 || kk[cell] <= 0.0)
return s;
s.pitch = pitch;
// f = 0 -> cell centre, f = 1 -> fired wire. Outside [0,1] = outside the band.
s.f = (cfrac - cfmin[cell]) / kk[cell];
// sign maps increasing f toward the fired cathode wire.
double sgn = (a1c1_zg[wf] >= zc) ? +1.0 : -1.0;
s.pcz = zc + sgn * s.f * half;
double fmax = dead_neighbor ? a1c1_missing_fmax : 1.0;
s.inband = (s.f >= 0.0 && s.f <= fmax);
// Reconstructed position should remain within one cell pitch of the fired wire.
s.pitchok = (TMath::Abs(s.pcz - zf) <= pitch);
return s;
}
inline A1C1Sol a1c1_solve(double cfrac, double zf, int cwire = -1, double anodeE = -1, int awire = -1)
{
A1C1Sol s{0, cfrac, zf, zf, {}, {}};
const double *cfmin = a1c1_cfmin_cell;
const double *kk = a1c1_k_cell;
if (a1c1_cfrac_split > 0.0 && cfrac >= 0.0 && cfrac < a1c1_cfrac_split)
{
s.band = 1;
if (a1c1_lowband_rfactor > 0.0 && cfrac > 0.0 && cfrac < 1.0)
{
double r = cfrac / (1.0 - cfrac);
r *= a1c1_lowband_rfactor;
cfrac = r / (1.0 + r);
}
else
{
cfmin = a1c1_cfmin2_cell;
kk = a1c1_k2_cell;
}
}
s.cfrac_used = cfrac;
int wf = 0;
for (int i = 1; i < 8; ++i)
if (TMath::Abs(a1c1_zg[i] - zf) < TMath::Abs(a1c1_zg[wf] - zf))
wf = i;
bool dead_neighbor = a1c1_missing_neighbor(awire, cwire); // same for both cells; hoist to avoid double scan
s.hi = solve_cell(wf - 1, wf, zf, cfrac, cfmin, kk, dead_neighbor); // cell above (higher z)
s.lo = solve_cell(wf, wf, zf, cfrac, cfmin, kk, dead_neighbor); // cell below (lower z)
s.pcz_hi = s.hi.pcz;
s.pcz_lo = s.lo.pcz;
return s;
}
double a1c1_side_perp_max = 20.0; // beam-axis Perp gate (mm)
// Which of the two candidate cells the beam-axis test selects.
enum class SideChoice
{
High, // the cell ABOVE the fired wire (pcz_hi)
Low // the cell BELOW the fired wire (pcz_lo)
};
inline SideChoice a1c1_pick_side(const TVector3 &si, double cx, double cy, double pcz_lo, double pcz_hi, int &status)
{
auto vtxZP = [&](double pcz, double &z, double &perp)
{
TVector3 pc(cx, cy, pcz);
TVector3 vtx = beamVertex(si, pc - si);
z = vtx.Z();
perp = beamPerp(vtx);
};
double zl, pl, zh, ph;
vtxZP(pcz_lo, zl, pl);
vtxZP(pcz_hi, zh, ph);
bool okl = (pl <= a1c1_side_perp_max);
bool okh = (ph <= a1c1_side_perp_max);
status = (okl || okh) ? ((okl && okh) ? 1 : 0) : 2;
if (okl && !okh)
return SideChoice::Low;
if (okh && !okl)
return SideChoice::High;
return (pl <= ph) ? SideChoice::Low : SideChoice::High; // both physical: smaller-Perp side
}
// a1c1_solve() + a1c1_pick_side() together, with the picked cell already
// resolved. Every call site that needs more than just the final pcz (side
// status, cell index, f, inband, pitchok -- e.g. for benchmark/diagnostic
// histograms) was previously re-deriving this same 4-line pattern by hand;
// this is that pattern, named once.
struct A1C1PickedSol
{
A1C1Sol sol;
SideChoice side = SideChoice::Low;
int side_status = -1;
const A1C1CellSol &best() const { return (side == SideChoice::High) ? sol.hi : sol.lo; }
};
inline A1C1PickedSol a1c1_solve_pick(double cfrac, double zf, const TVector3 &si, double cx, double cy,
int cwire = -1, double anodeE = -1, int awire = -1)
{
A1C1PickedSol out;
out.sol = a1c1_solve(cfrac, zf, cwire, anodeE, awire);
out.side = a1c1_pick_side(si, cx, cy, out.sol.pcz_lo, out.sol.pcz_hi, out.side_status);
return out;
}
// Full pipeline: raw anode/cathode energies -> cfrac -> solve -> pick side ->
// picked Z, plus whether the pick landed in-band. This is what most call
// sites actually want (they don't need the intermediate A1C1Sol/side_status
// unless they're doing benchmark diagnostics -- for that, call
// a1c1_solve_pick directly instead).
//
// Takes primitives rather than TrackRecon.C's `Event` type so this header
// has no dependency on TrackRecon.C's class definitions; see the thin
// `Event`-taking overload kept in TrackRecon.C next to the `Event` class
// for the short call-site spelling existing code uses.
inline double a1c1_cfrac_pcz(double pcz_raw, double energyAnode, double energyCathode,
double cx, double cy, int cathodeCh, int anodeCh,
const TVector3 &si, bool &inband)
{
inband = false;
double ac = energyAnode + energyCathode;
double cfrac = (ac > 0.0) ? energyCathode / ac : -1.0;
if (cfrac < 0.0)
return pcz_raw;
A1C1PickedSol picked = a1c1_solve_pick(cfrac, pcz_raw, si, cx, cy, cathodeCh, energyAnode, anodeCh);
const A1C1CellSol &best = picked.best();
inband = (best.inband && picked.side_status != 2);
return best.pcz;
}
// ---------------------------------------------------------------------
// A1C2: two-cathode "step ladder" Z reconstruction
// ---------------------------------------------------------------------
//
// Same step-ladder, pivot-about-cell-midpoint model as Armory/
// PC_StepLadder_Correction.h's model_invert. That file is shared with
// MakeVertex.C (a separate, parallel branch of development, out of scope
// here) and a few scratch macros, so rather than include it or edit it,
// this is a standalone copy -- keeping every TrackRecon.C reconstruction
// path (A1C0/A1C1/A1C2) self-contained in this one header rather than
// reaching into a file with unrelated consumers. If the underlying model
// ever changes, both copies need the same edit; there are exactly two.
//
// Rewritten as a plain scalar function rather than kept in model_invert's
// original `double f(double *y, double *p)` shape -- that signature only
// existed to match TF1's raw-function-pointer constructor. TrackRecon.C
// used to go through a `TF1 pcfix_func` purely to get a callable out of it,
// calling only pcfix_func.Eval(z) at every site (npar `p` was never used,
// and nothing called .Draw()/.Integral()/anything else TF1-specific) --
// so the TF1 wrapper bought nothing. a1c2_zfix has the same scalar
// in/scalar out shape as a1c0_wirePos/a1c1_solve above instead.
inline double a1c2_zfix(double z)
{
double result = z;
double slope = 0.52;
double z_grid[8] = {147.998, 101.946, 59.7634, 19.6965, -19.6965, -59.7634, -101.946, -147.998};
for (int i = 0; i < 7; i++)
{
if (z <= z_grid[i] && z > z_grid[i + 1])
{
double zavg = (z_grid[i] + z_grid[i + 1]) * 0.5; // midpoint about which we pivot
result = (z - zavg) / slope + zavg;
break;
}
}
return result;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
0 2.270700e-05 0.000000e+00 1
1 3.849247e-05 0.000000e+00 1
2 2.478595e-05 0.000000e+00 1
3 2.418520e-05 0.000000e+00 1
4 2.473039e-05 0.000000e+00 1
5 6.110646e-05 0.000000e+00 2
2 3.849247e-05 0.000000e+00 1
3 3.849247e-05 0.000000e+00 1
4 3.849247e-05 0.000000e+00 1
5 4.489228e-05 0.000000e+00 2
6 4.489228e-05 0.000000e+00 1
7 3.374396e-05 0.000000e+00 1
8 3.370297e-05 0.000000e+00 1