Where
What happens
-
GEM points are projected twice. The residual loop iterates with auto& event and rescales GEM_x/GEM_y to the HyCal plane in place (*= HC_z / GEM_z[det]). This happens once in the all-4-matched block, then again in the {0,2} block and again in the {1,3} block. GEM_z is never updated, so an event with all four chambers matched ends up with the points scaled by (HC_z/GEM_z)^2 instead of HC_z/GEM_z. Events where only one pair matches are handled correctly.
- With the default PRad-II survey (
database/runinfo/general.json: HyCal z = 6265.887, GEM z = 5813.831 / 5853.713 / 5411.839 / 5456.658 mm), the extra factor is about 1.078 / 1.070 / 1.158 / 1.148 for GEM0..3. For the X17 geometry it is about 1.065 / 1.059 / 1.130 / 1.122.
- Example: a raw GEM0 hit at y = 100 mm should project to 107.8 mm, but it is filled as 116.2 mm. That is 8.4 mm off, against a histogram range of ±20 mm.
- For a perfectly aligned setup, the left-layer residual
GEM_x[0] - GEM_x[2] from these events becomes ≈ (1.078 − 1.158)·x ≈ −0.08·x instead of 0. The right-layer residual becomes ≈ −0.078·x.
- Affected histograms:
h1_deltaX/Y_gem_hycal{0..3}, h1_deltaX/Y_gem_layer_left/right and h2_deltaX_vs_deltaY_gem_left/right. The up/down histograms filled inside the all-4 block are correct.
GEM_layer_left_* and GEM_layer_right_* are applied to gem_x/gem_y of GEM0 and GEM1 in the output run config (L558-L563). deltaX/Y_gem_hycal_* are written to the alignment summary.
-
The Moller coplanarity cut is one-sided.
float phi1 = std::atan2(ev.cl_y[0], ev.cl_x[0]) * 180.0 / M_PI;
float phi2 = std::atan2(ev.cl_y[1], ev.cl_x[1]) * 180.0 / M_PI;
...
if (std::abs(phi1 - phi2) -180.0 > 8.0) continue;
This only rejects pairs with |Δφ| > 188°. Every pair with |Δφ| ≤ 188° passes, including same-side pairs (for example φ1 = 30°, φ2 = 20°). Only the energy cuts are left to reject them. The selected Moller sample feeds the HyCal/GEM Moller-center, z-distance and HyCal−GEM phi-difference fits. Those fits set target_x/y, hycal_z, gem_z and gem_tilt_z in the output run config.
Evidence
for (auto& event : all_mott_events) {
if (event.match[0] && event.match[1] && event.match[2] && event.match[3]) {
for (int det = 0; det < 4; ++det) {
float scale = event.HC_z / event.GEM_z[det];
event.GEM_x[det] *= scale; // 1st projection
event.GEM_y[det] *= scale;
}
...
}
if (event.match[0] && event.match[2]) {
for (int det : {0, 2}) {
float scale = event.HC_z / event.GEM_z[det];
event.GEM_x[det] *= scale; // 2nd projection of the same event
event.GEM_y[det] *= scale;
h1_deltaX_gem_hycal[det]->Fill(event.GEM_x[det] - event.HC_x);
...
Other tools in the repo already use the two-sided phi cut: analysis/tools/leakage_correction_check.cpp:313 (std::abs(std::fabs(phi1 - phi2) - 180.f)) and analysis/tools/quick_check.cpp:935.
Suggested fix
- Project each matched GEM point once at the top of the loop, then use the projected values in all three blocks. Guard with
match[det], because unmatched entries have GEM_z = 0.
for (auto& event : all_mott_events) {
for (int det = 0; det < 4; ++det) {
if (!event.match[det]) continue;
const float scale = event.HC_z / event.GEM_z[det];
event.GEM_x[det] *= scale;
event.GEM_y[det] *= scale;
}
// all-4 / {0,2} / {1,3} blocks: fill only, no further scaling
}
- Make the phi cut two-sided:
if (std::abs(std::abs(phi1 - phi2) - 180.0) > 8.0) continue;
- Both fixes change
det_align outputs (the iterated run config and the summary), so earlier alignment iterations should be re-run.
Status
Still present on the dedup-refactor branch. The scaling there is a project(det) lambda that is still called in all three blocks, and the phi cut is unchanged. Both were left alone so the refactor stays output-identical.
Where
What happens
GEM points are projected twice. The residual loop iterates with
auto& eventand rescalesGEM_x/GEM_yto the HyCal plane in place (*= HC_z / GEM_z[det]). This happens once in the all-4-matched block, then again in the{0,2}block and again in the{1,3}block.GEM_zis never updated, so an event with all four chambers matched ends up with the points scaled by(HC_z/GEM_z)^2instead ofHC_z/GEM_z. Events where only one pair matches are handled correctly.database/runinfo/general.json: HyCal z = 6265.887, GEM z = 5813.831 / 5853.713 / 5411.839 / 5456.658 mm), the extra factor is about 1.078 / 1.070 / 1.158 / 1.148 for GEM0..3. For the X17 geometry it is about 1.065 / 1.059 / 1.130 / 1.122.GEM_x[0] - GEM_x[2]from these events becomes ≈ (1.078 − 1.158)·x ≈ −0.08·x instead of 0. The right-layer residual becomes ≈ −0.078·x.h1_deltaX/Y_gem_hycal{0..3},h1_deltaX/Y_gem_layer_left/rightandh2_deltaX_vs_deltaY_gem_left/right. The up/down histograms filled inside the all-4 block are correct.GEM_layer_left_*andGEM_layer_right_*are applied togem_x/gem_yof GEM0 and GEM1 in the output run config (L558-L563).deltaX/Y_gem_hycal_*are written to the alignment summary.The Moller coplanarity cut is one-sided.
This only rejects pairs with |Δφ| > 188°. Every pair with |Δφ| ≤ 188° passes, including same-side pairs (for example φ1 = 30°, φ2 = 20°). Only the energy cuts are left to reject them. The selected Moller sample feeds the HyCal/GEM Moller-center, z-distance and HyCal−GEM phi-difference fits. Those fits set
target_x/y,hycal_z,gem_zandgem_tilt_zin the output run config.Evidence
Other tools in the repo already use the two-sided phi cut:
analysis/tools/leakage_correction_check.cpp:313(std::abs(std::fabs(phi1 - phi2) - 180.f)) andanalysis/tools/quick_check.cpp:935.Suggested fix
match[det], because unmatched entries haveGEM_z = 0.det_alignoutputs (the iterated run config and the summary), so earlier alignment iterations should be re-run.Status
Still present on the
dedup-refactorbranch. The scaling there is aproject(det)lambda that is still called in all three blocks, and the phi cut is unchanged. Both were left alone so the refactor stays output-identical.