Migrate calo calibration modules from CaloCalibration repository - #1936
Migrate calo calibration modules from CaloCalibration repository#1936giro94 wants to merge 4 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
Do you mean within this CaloCalibration folder or the number of CaloXXX folders in Offline? @bechenard |
|
📝 The HEAD of |
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.