Migrate calo calibration modules from CaloCalibration repository - #1936
Migrate calo calibration modules from CaloCalibration repository#1936giro94 wants to merge 3 commits into
Conversation
|
Unsure if we should keep the inner directory structure (CosmicsCalib, NoiseAnalysis, etc), or we should flatten everything into inc/src/fcl directories. @brownd1978 |
|
☀️ The build tests passed at c41473e.
N.B. These results were obtained from a build of this Pull Request at c41473e after being merged into the base branch at 0878d6b. For more information, please check the job page here. |
oksuzian
left a comment
There was a problem hiding this comment.
PR Review Summary — #1936
Reviewed at head c41473e9eb9851233e4da5ea8beef3876c2bc38b. First pass.
Decision
- 🔴 request changes
Scope understood
- Migrates four calorimeter calibration modules out of
Mu2e/CaloCalibrationintoOffline/CaloCalibration/:CosmicsCalib(CaloCosmicEnecalib,CaloCosmicEnergy,caloT0alig) andNoiseAnalysis(BaselineAnalyzerplusanalyzeBaselines.fcl). Source and combination steps are stated as following later. - The stated intent is that anything needed at online level or in Pass-1/2/N lives in Offline; the rest stays in the separate repo.
- Note on the prior review: @sophiemiddleton approved at this same head with an empty body, so there are no findings to carry forward. The blocker below is a build-system issue that an approval does not address; the CI green also does not cover it, for the reason given in finding 1.
Findings
-
🔴 [S0] The CMake build of Offline no longer configures.
- Evidence:
CMakeLists.txt:126addsadd_subdirectory(CaloCalibration), but at this headCaloCalibration/contains onlyCosmicsCalib/andNoiseAnalysis/— there is noCaloCalibration/CMakeLists.txt
(gh api repos/Mu2e/Offline/contents/CaloCalibration?ref=c41473e9returns exactly those two entries). Reproduced against the real cmake:Adding the missing intermediate file then exposes a second, independent error, becauseCMake Error at CMakeLists.txt:3 (add_subdirectory): The source directory .../CaloCalibration does not contain a CMakeLists.txt file.CaloCalibration/NoiseAnalysis/CMakeLists.txt:16begins with a stray%:CMake Error at CaloCalibration/NoiseAnalysis/CMakeLists.txt:1: Parse error. Expected a command name, got unquoted argument with text "%install_headers".CaloCalibration/CosmicsCalib/has noCMakeLists.txtat all, so its three modules would not be built by CMake even once configuration succeeds. - Why CI is green anyway, and why this is not caught:
mu2e/buildtestis the scons-via-Muse build, and it did compile and link all four modules (scons.loglines 713-780, including-Wl,--no-undefined). Thecheck_cmakejob iteratesfor dir in $PWD/*and only descends where$dir/srcexists (bin/check_cmake.sh:33, guarded by the-d $dir/srctest at:8), so a two-level package is invisible to it — it reported success without ever looking atCaloCalibration. - Suggested fix: add
CaloCalibration/CMakeLists.txtwithadd_subdirectory(CosmicsCalib)andadd_subdirectory(NoiseAnalysis); addCaloCalibration/CosmicsCalib/CMakeLists.txtwith acet_build_pluginblock per module; and inNoiseAnalysis/CMakeLists.txtdrop the%and use the spelling the rest of the repo uses,install_headers(USE_PROJECT_NAME SUBDIRS inc)— or drop that line entirely, since there is noinc/directory here. Two smaller items in the same file:install_fhicl(SUBDIRS fcl SUBDIRNAME CaloCalibration/NoiseAnalysis/fcl)is missing theOffline/prefix that every otherinstall_fhiclin the repo carries, and the file has no trailing newline.
- Evidence:
-
🔴 [S0] Out-of-bounds writes in
CaloCosmicEnergywhen a hit lands exactly on the top of the energy range.- Evidence:
CaloCosmicEnergy_module.cc:408-416if (sipm_mean_e <= 55.) { int whichband = sipm_mean_e / 5; Energy_band[whichband] += sipm_mean_e; counter_energy_band[whichband]++; LR[whichband]->Fill(...); ALR[whichband]->Fill(...); CryALR[crystal_id][whichband]->Fill(...); Cry_Energy_band[crystal_id][whichband] += sipm_mean_e; Cry_counter_energy_band[crystal_id][whichband]++; }
Ebinis 11 (:98) and all five of those arrays are dimensioned[Ebin](:120-122,:128-129). A hit withenergyDep()of exactly 55.0 MeV giveswhichband == 11, one past the end of every one of them.LR,ALRandCryALRare arrays ofTH1F*, so the write is preceded by a read of an out-of-range pointer which is then dereferenced through->Fill(). A negativeenergyDep()— which the reconstruction can produce on a noise-dominated channel — indexes at-1by the same path, since the guard has no lower bound. - Impact: heap corruption or a segfault in a calibration job, dependent on input values, so it will not show up reliably in a short test.
- Suggested fix: make the guard exclusive and two-sided —
if (sipm_mean_e >= 0. && sipm_mean_e < Ebin * 5.)— and derive the band width from a named constant rather than the literal5repeated at:186,:204,:207and:409.
- Evidence:
-
🟠 [S1]
std::stringconstructed from a possibly-nullgetenv, with the emptiness check placed after the fact.- Evidence: three sites.
caloT0alig_module.cc:142std::string _fileT0 = getenv("MUSE_WORK_DIR");,caloT0alig_module.cc:446-450andCaloCosmicEnergy_module.cc:433-437:Constructingstd::string outDir = std::getenv("OUTDIR"); if (outDir.length() == 0) { mf::LogError("OUTDIR-NOT-SET") << "Environmental variable for calib output file not set "; }
std::stringfrom a null pointer is undefined behaviour, so theLogErrorbelow it can never run for the case it is written for; and when it does run, execution continues and the job writes to/tcorr.datand/calib_parameters.datat the filesystem root.MUSE_WORK_DIRin particular is set by Muse and will not be present in a CMake/spack-installed release, which is the environment this migration is meant to serve. - Impact: a segfault, or output silently written outside the intended directory, from an unset environment variable.
- Suggested fix:
CaloCosmicEnecalibin this same PR already shows the pattern to follow — anOutCalibFilefhicl atom, opened in the constructor, withthrow cet::exception(...)when the open fails (CaloCosmicEnecalib_module.cc:73-75,:187-190). Give the other two modules the same treatment and drop thegetenvcalls; the T0 data file path should likewise come from fhicl rather than fromMUSE_WORK_DIR.
- Evidence: three sites.
-
🟠 [S1] The migration is incomplete:
caloT0aligreads a data file that was not moved, and neither was any fcl forCosmicsCalib.- Evidence:
caloT0alig_module.cc:142-157reads$MUSE_WORK_DIR/CaloCalibration/CosmicsCalib/data/t0s_allchan_1ns.dat. That file exists in the source repo (Mu2e/CaloCalibration→CosmicsCalib/data/t0s_allchan_1ns.dat) but is not in this PR —CaloCalibration/CosmicsCalib/contains onlysrc.CosmicsCalib/vst/was not migrated either, andNoiseAnalysisis the only one of the two packages that brings itsfcl/directory across. - Impact:
if (T0File.is_open())simply fails and the job proceeds withToff[]all zeros — no warning, no error, and a plausible-looking set of residuals out the far end. The threeCosmicsCalibmodules also have no runnable configuration in Offline, so nothing in the repo exercises them. - Suggested fix: bring
data/and a driver fcl across with the modules, and treat a missing T0 file as fatal rather than as a silent zero. Worth stating explicitly in the PR body ifvst/is deliberately staying behind.
- Evidence:
-
🟠 [S1]
caloT0aligwrites into fixed arrays using an index read straight out of a text file.- Evidence:
caloT0alig_module.cc:150-151while (T0File >> iChanT0 >> TvalT0) { Toff[iChanT0] = TvalT0; ... }and:174-175while (inpFile >> iChan >> Tval >> ...) { Tcor[iChan] = Tval; ... }.ToffandTcorarefloat[nROchan]withnROchan == 2696(:99-102), and neither loop bounds-checks the index. The count is only checked afterwards, at:181, and only for the second file. - Impact: a stale or corrupted calibration file — exactly the class of input this iterative procedure regenerates each pass — overwrites arbitrary memory.
- Suggested fix: reject
iChan < 0 || iChan >= nROchaninside both loops with acet::exception.
- Evidence:
-
🟠 [S1]
CaloCosmicEnergydivides by a path length that its own helper can return as zero.- Evidence:
CaloCosmicEnergy_module.cc:759-849—findpathinitialisesfloat path = 0;and has anelsebranch (:840-844) that assigns nothing when no crystal face is crossed; it also leavesxup/xlow/yleft/yrightat zero whenm == 0(:769). The result is used unguarded at:384:... ->energyDep() * cryDim / path[iCry]. - Impact: an infinity is filled into
hSiPMfp, where it lands in the overflow bin and quietly biases the normalized-track MPV. - Suggested fix: the newer
CaloCosmicEnecalibalready guards this —else if ((chi2norm < CutChi2Norm) && (path[kk] > 0))atCaloCosmicEnecalib_module.cc:429. Apply the same guard here, and havefindpathsignal "no path" explicitly rather than returning a value that reads as a real length.
- Evidence:
Smaller items
- 🟡 Dead code, several kinds:
caloT0alig_module.cc:424-426is unreachable afterreturn retval;at:422;int diag = 0;with a dozenif (diag == 1)blocks that can never run appears in bothCaloCosmicEnergy_module.cc:766andCaloCosmicEnecalib_module.cc:602; commented-out code atcaloT0alig_module.cc:468-470,CaloCosmicEnergy_module.cc:213-215and:236-241, andanalyzeBaselines.fcl:18(#@local::Services.Reco);TFitResultPtr fitresultatCaloCosmicEnergy_module.cc:315is never read;_nProcessed/_nFilteredare counted incaloT0aligand never reported anywhere. - 🟡
CaloCosmicEnergy_module.cc:292assignsmax_y = PosX[h];inside the loop that is scanningPosY.Dyis only ever printed at_diagLevel > 0(:303), so nothing downstream is wrong today, but the variable is both mis-computed and otherwise unused — either fix it or drop it. The two loops at:273and:289are also labelled "bubble sort" when they are min/max scans. - 🟡
caloT0alig_module.cc:326-333mixes an SiPM-local id with a vector position:idxisCaloSiPMId::SiPMLocalId(), which is_id % 2(DataProducts/inc/CaloSiPMId.hh:25), but it is then used to subscripthit.recoCaloDigis().at(idx)while the loop itself runs overiCha. On a crystal with a single surviving readout whose local id is 1,.at(1)throwsstd::out_of_range; where both are present but not stored in local-id order, one digi is read twice and the other never. Index withiChaand use the local id only where a local id is meant. - 🟡 Silent degradation in
BaselineAnalyzer: a CSV that will not open producesstd::cout << "Warning! ..."and setswriteCSV_ = false(:272-278), so the job exits 0 having produced no thresholds; and channels with no data are given a fabricated baseline of 2048 that is then written into the threshold CSV alongside the measured ones (:401-412), with nothing in the file marking them as defaults. Both should be errors. - 🟡 Numbers that already have a home elsewhere.
BaselineAnalyzer_module.cchardcodes16100(:139,:510) whereCaloConst::_nDIRACis 161,20(:403,:418,:424,:567) where it usesCaloConst::_nChPerDIRACcorrectly at:182,board < 80for the disk split (:382), and2048as the pedestal (:136-137,:408).CaloCosmicEnergy_module.ccfills the position error with9.81fand a comment deriving it from a 34 mm crystal (:231) while reading the real crystal dimension from the geometry twelve lines earlier (:156), and carries three different vertical-track thresholds —Dx < 33(:325),Dx < 35(:368) andMaxDxVertical = cryDim * 1.1(:158,:340).caloT0alig_module.cc:310uses3.1416for π and:94redefines the speed of light ascvel = 299.792458rather thanCLHEP::c_light. - 🟡
CaloCosmicEnergyandCaloCosmicEnecalibcarry verbatim copies offindpath(~70 lines), of the Landau-Gauss convolution (langaufun/langaus), and of the9.81/144./cryDim * 1.1constants. Since the header ofCaloCosmicEnecalib_module.cc:5-6describes it as the successor toCaloCosmicEnergy, it is worth saying in the PR body whether both are meant to live in Offline long-term; if they are, the shared pieces belong in one place. - 🟡
BaselineAnalyzer'swriteTXT/TXTfoldername,writeCSV/CSVfilenameandwritePDF/PDFfilenameare the flag-plus-loose-atoms shape thatfhicl::OptionalTable<Config>exists for; withwriteTXT: trueand the default empty folder the module writes to/dirac000.baseline. Separately, the C++ defaults arethresholdOffset = 100,thresholdOffsetPin = 50(:73-74) whileanalyzeBaselines.fcl:56-57sets50and100— the two are swapped relative to each other, which is worth confirming is deliberate. - ⚪ Collapsed nits:
BaselineAnalyzer_module.cc:12-13and:19-22include five artdaq headers (Fragment,ContainerFragment,EventHeader,DTCEventFragment,CalorimeterDataDecoder,FragmentType) that the module never uses, and those are what pull the fourartdaq-core*entries into its link list;TFile,TEllipse,TTreeand<sys/stat.h>are unused there too, as areGlobalConstantsHandle.hh,TDirectory.h,Selector.handSequence.hincaloT0alig_module.cc. ClasscaloT0aligstarts lowercase where the repo capitalises type names; itsbeginJob/endJob/filterarevirtualwithoutoverride(:78-80). Prints go tostd::coutrather than message-facility throughout, several of them unguarded by any verbosity flag (BaselineAnalyzer_module.cc:275,:291,:331,:434). The "*** TO BE IMPLEMENTED ***" markers atcaloT0alig_module.cc:132and:244are real TODOs that the FIXME/TODO CI counter does not match on.
On the directory-structure question
Keeping CosmicsCalib/NoiseAnalysis as subdirectories is fine and has precedent: ExtinctionMonitorFNAL/ is a two-level package whose top-level CMakeLists.txt is nothing but seven add_subdirectory lines plus its own install_fhicl, and each leaf carries the usual cet_build_plugin / install_source / install_headers block. Copying that shape is exactly what finding 1 asks for. The one real cost is the check_cmake.sh blindness described above, which ExtinctionMonitorFNAL shares — that is a pre-existing gap in the CI script, not something this PR introduced, but it does mean a nested package gets less automatic protection than a flat one.
Validation check
- Build/tests run: partial.
mu2e/buildtestis green at this head and genuinely compiled and linked all four modules under-Werror(scons.log:713-780); whitespace clean; FIXME/TODO 0 in 4 files; clang-tidy reported 8 errors / 942 warnings, which I did not attribute. I reproduced the two CMake errors in finding 1 against a minimal tree with the same file layout. - Config contract check: pass for the one new fcl.
fhicl-dump Offline/CaloCalibration/NoiseAnalysis/fcl/analyzeBaselines.fclunderSimJob/MDC2025avwith an appended shim exits 0 and resolves to 1216 lines;CaloDigisFromDTCEvents(DAQ/src/) andCaloVisualizer/inc/THMu2eCaloDisk.hhboth exist in Offline at this head.CosmicsCalibhas no fcl to check. - Cross-repo consistency: needs follow-up — see finding 4 for what stayed behind in
Mu2e/CaloCalibration.
Residual risk
- I did not review the physics of the calibration procedures themselves (langaus fitting strategy, the asymmetry-to-Npe inversion, the T0 iteration scheme), only their implementation.
- Nothing in Offline runs the three
CosmicsCalibmodules, so none of them has runtime coverage here; the failure modes in findings 2, 5 and 6 are reached by particular input values and would not surface in a short smoke test.
Author follow-ups
- Add
CaloCalibration/CMakeLists.txtandCaloCalibration/CosmicsCalib/CMakeLists.txt, and fix the%install_headersline, the missingOffline/prefix oninstall_fhicl, and the missing trailing newline inNoiseAnalysis/CMakeLists.txt. Please confirm with a local CMake configure, since buildtest will not catch it. - Bound the energy-band index in
CaloCosmicEnergyand the file-read indices incaloT0alig. - Replace the three
getenvcalls with fhicl parameters that throw when the target cannot be opened, followingCaloCosmicEnecalib. - Migrate
CosmicsCalib/data/and a driver fcl, or say in the PR body what is deliberately staying inMu2e/CaloCalibration. - Guard the
cryDim / pathdivision inCaloCosmicEnergy. - Say whether
CaloCosmicEnergyandCaloCosmicEnecalibare both intended to live here long-term; if so, the duplicatedfindpathand langaus code should get a single home.
bechenard
left a comment
There was a problem hiding this comment.
Ok, but I would like to avoid creating 50 Calo folders in the future
For now, cosmics and noise modules moved over.
Source and combination will follow later.
The idea is to move here anything that must run at online level or in Pass-1/2/N steps.
Anything else can remain in the separate repo.