Skip to content

Pawel plesniak/absorber shift - #1875

Open
PawelPlesniak wants to merge 19 commits into
Mu2e:mainfrom
PawelPlesniak:PawelPlesniak/AbsorberShift
Open

Pawel plesniak/absorber shift#1875
PawelPlesniak wants to merge 19 commits into
Mu2e:mainfrom
PawelPlesniak:PawelPlesniak/AbsorberShift

Conversation

@PawelPlesniak

@PawelPlesniak PawelPlesniak commented Jul 7, 2026

Copy link
Copy Markdown

This PR addresses several issues found when completing the corrections to my normalization studies.

Changes are summarized in the bullet point list for each file affected by core code changes. Critically note - there are several other files that are also affected, but the pre-commit config highlighted many changes that needed to happen before my changes could go in (after rebasing with branch main). These changes have been addressed.

  • CountVirtualDetectorHits_module.cc - this makes the vector of virtual detectors being summarized in the model unique, my mapping to and back from an unordered set
  • PhotonGun_module.cc - this now allows for a set of photons to travel in a straight line from the origin to target position, defined by a direction vector as delta_x, delta_y, and delta_z. This vector is mapped to the unit vector, and the momentum is scaled accordingly.
  • AbsorberFromSTHPGe.fcl - driver code that will allow future unfolding studies to be performed, especially important for the HPGe where additional absorber is present, and the results have not yet been fully quantified.
  • AbsorberFromSTLaBr.fcl - as above but for LaBr.
  • EventFilter.fcl - debugging tool to help with ongoing simulation studies. Allows for looking at individual events in a data file. For my studies, it was used to demonstrate the behaviour of the ZS and MWD algorithms on a macro-spill worth of beam-on time.
  • FirePhotonToDetector.fcl - driver code that fires singular photons to a detector of choice, aimed for use with studying the STM detector waveform generators and analysis tools.
  • STMMC/fcl/prolog.fcl - rewritten to define the simulation parameters more clearly
  • ROOTAnalysisDump.fcl - driver code to convert the art files into ROOT format for easy plotting. I will not be rewriting this for the current analysis framework as I do not have the time for this.
  • HPGeWaveformsFromStepPointMCs_module.cc - addresses issues with the waveform generator, namely
    • MWD was failing with this implementation as the waveform amplitudes were set to zero during microspills that did not have photons interacting with the detector crystal, throwing off MWD
    • Concatenated events were discontinuous
    • Added preamplifier charge collection response function
  • SimParticleAndVDBacktrace_module.cc - new module generating ROOT files with data that backtraces of STM signal photons back through the SimParticle provenance. The code to plot these will be merged in the STMAnalysis repository
  • SimParticleDump_module.cc - dumps the full SimParticle provenance to ROOT files for plotting, with a more compact format than the previous module.
  • STMResamplingProducer_module.cc - Initial simulations had different data product names for SimParticles propagated with the BeamToVD and BeamToVD1809 drivers, this change standardizes them.
  • VirtualDetectorTree_module.cc - add more data to ROOT files generated for plotting.
  • STMMovingWindowDeconvolution_module.cc - some more data for visualizing the behaviour of analyzed waveforms

I would like to ask some questions

  • Why was the STMMWDDigi renamed to STMPHDigi, and what is the difference in analysis chains?

@FNALbuild

Copy link
Copy Markdown
Collaborator

Hi @PawelPlesniak,
You have proposed changes to files in these packages:

  • STMMC
  • STMReco
  • Analyses
  • EventGenerator
  • RecoDataProducts

which require these tests: build.

@Mu2e/write, @Mu2e/fnalbuild-users have access to CI actions on main.

⌛ The following tests have been triggered for 3bb3d47: build (Build queue - API unavailable)

About FNALbuild. Code review on Mu2e/Offline.

@FNALbuild

Copy link
Copy Markdown
Collaborator

‼️ It was not possible to prepare the workspace for this test. This is often caused by merge conflicts - please check and try again.

> git diff --check | grep -i conflict
EventGenerator/src/PhotonGun_module.cc:69: leftover conflict marker
EventGenerator/src/PhotonGun_module.cc:71: leftover conflict marker
EventGenerator/src/PhotonGun_module.cc:74: leftover conflict marker
RecoDataProducts/inc/STMWaveformDigi.hh:21: leftover conflict marker
RecoDataProducts/inc/STMWaveformDigi.hh:29: leftover conflict marker
RecoDataProducts/inc/STMWaveformDigi.hh:40: leftover conflict marker
RecoDataProducts/inc/STMWaveformDigi.hh:48: leftover conflict marker
RecoDataProducts/inc/STMWaveformDigi.hh:53: leftover conflict marker
RecoDataProducts/inc/STMWaveformDigi.hh:56: leftover conflict marker
RecoDataProducts/inc/STMWaveformDigi.hh:64: leftover conflict marker
RecoDataProducts/inc/STMWaveformDigi.hh:66: leftover conflict marker
RecoDataProducts/inc/STMWaveformDigi.hh:68: leftover conflict marker
STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:66: leftover conflict marker
STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:70: leftover conflict marker
STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:76: leftover conflict marker
STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:84: leftover conflict marker
STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:86: leftover conflict marker
STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:88: leftover conflict marker
STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:101: leftover conflict marker
STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:104: leftover conflict marker
STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:109: leftover conflict marker
STMMC/fcl/prolog.fcl:158: leftover conflict marker
STMMC/fcl/prolog.fcl:160: leftover conflict marker
STMMC/fcl/prolog.fcl:162: leftover conflict marker
STMMC/fcl/prolog.fcl:174: leftover conflict marker
STMMC/fcl/prolog.fcl:178: leftover conflict marker
STMMC/fcl/prolog.fcl:188: leftover conflict marker
STMReco/src/STMMovingWindowDeconvolution_module.cc:118: leftover conflict marker
STMReco/src/STMMovingWindowDeconvolution_module.cc:120: leftover conflict marker
STMReco/src/STMMovingWindowDeconvolution_module.cc:130: leftover conflict marker
STMReco/src/STMMovingWindowDeconvolution_module.cc:317: leftover conflict marker
STMReco/src/STMMovingWindowDeconvolution_module.cc:326: leftover conflict marker
STMReco/src/STMMovingWindowDeconvolution_module.cc:330: leftover conflict marker
Test Result Details
test with Command did not list any other PRs to include
merge 3bb3d47 into 704459c merge failed

PawelPlesniak added 3 commits July 7, 2026 06:50
@PawelPlesniak

Copy link
Copy Markdown
Author

I leave this for review by @YongyiBWu and @AndrewEdmonds11

@PawelPlesniak
PawelPlesniak marked this pull request as ready for review July 7, 2026 12:36
@PawelPlesniak

Copy link
Copy Markdown
Author

Note - I will review any failures that the CI workflow raises

@PawelPlesniak

Copy link
Copy Markdown
Author

@FNALbuild run build test

@FNALbuild

Copy link
Copy Markdown
Collaborator

⌛ The following tests have been triggered for b19652b: build (Build queue - API unavailable)

@PawelPlesniak

Copy link
Copy Markdown
Author

@YongyiBWu and @AndrewEdmonds11 The CI workflow issues have been addressed, this is now ready for review

@oksuzian

oksuzian commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

🔴 Bugs (likely functional problems)

1. SimParticleDump_module.cc — empty-file counter never resets, and threshold check is wrong

if (!stepHandle || stepHandle->empty()) { consecutiveEmptyFileCounter++; return; }   // returns BEFORE threshold check
...
if (consecutiveEmptyFileCounter > consecutiveEmptyFileThreshold)
    throw cet::exception("LogicError", "Too many consecutive empty files, stopping the job");

Because the empty branches return immediately, the threshold throw is only ever reached on a non-empty event — the exact opposite of intended. The counter is also never reset to 0 on success in the early-return paths. Compare with SimParticleAndVDBacktrace / VirtualDetectorTree, which check the threshold before returning. The author also flags this file as unvalidated: // TODO before upload - validate that the code works.

2. HPGeWaveformsFromStepPointMCs_module.ccdigitize() clipping logic is inverted

ADC = _chargeDecayed[i] * chargeToADC;
_adcs[i] = (ADC > ADCMax) ? static_cast<int16_t>(std::round(ADC)) : ADCMax;

ADCMax is the most-negative value (-2^15 + 1), and the waveform is negative-going. This keeps the rounded value only when ADC > ADCMax and otherwise floors it to ADCMax — that clamps from below but nothing prevents positive overflow, and the ternary condition reads backwards relative to its intent. Worth double-checking the direction against the validation if (j > 1000) throw ... "ADC values too high!" on line 355.

3. HPGeWaveformsFromStepPointMCs_module.cc — validation constant defaultMicrospillBufferLengthCount < 1 is unreachable

if (defaultMicrospillBufferLengthCount < 1)
    throw cet::exception("RANGE", "defaultMicrospillBufferLengthCount has to be at least 1!\n");

defaultMicrospillBufferLengthCount is a hard-coded const int = 2, so this guard can never fire. The check should validate the user-supplied microspillBufferLengthCount instead.

4. PhotonGun_module.cc — new energy check rejects valid low-energy photons

if (E < std::numeric_limits<double>::epsilon())
    throw cet::exception("RANGE") << "Energy must be greater than zero, exiting.";

epsilon() (~2.2e-16) is a floating-point precision tolerance, not "zero". Any legitimately small energy below it is rejected. Use E <= 0.0. (The good news: this diff fixes a real copy-paste bug on the same lines — px = conf().py()py = conf().py().)

5. HPGeWaveformGenerationAndAnalysis.fcl — dangling data-product tag after module rename
The producer PHHPGe was renamed to MWDHPGe, but the output still references the old name:

"keep mu2e::STMPHDigis_PHHPGe_*_HPGeReco"

With no PHHPGe module in any path, this keep will drop the intended digis. Should be MWDHPGe.

🟠 Correctness / robustness concerns

6. Uninitialized counters read in summaries — In both STMMovingWindowDeconvolution_module.cc (processedADCs) and HPGeWaveformsFromStepPointMCs, some counters are declared without an initializer (uint ... processedADCs;) and later printed / accumulated (processedADCs += nADCs). Undefined initial value. Initialize to 0.

7. SimParticleDump uses SimParticles.at(step.trackId()).at() on the SimParticleCollection throws if the track id isn't present; other modules in this PR guard more defensively. Given the file is explicitly unvalidated, worth confirming the key type/lookup.

8. Hard-coded 320 MHz couplingproduce() now throws unless fADC == 320, and decayCharge() hard-codes the 320 MHz assumption for the preamplifier rise time. The author acknowledges this (// TODO - this only keeps accurate time if the sampling is 320MHz), but it silently makes the fADC fhicl parameter a lie for any other value.

🟡 Minor / cleanup

  • Typo: unqiueEnabledVDsVec in CountVirtualDetectorHits_module.cc, and duplicate ;; on the same line.
  • Debug leftovers: HPGeWaveformsFromStepPointMCs carries waveform_time with a self-described // TODO - REMOVE ME!, plus kept_events/dropped_events labelled "for debugging".
  • Copy-pasted banner: STMMovingWindowDeconvolution::beginJob() prints "STM HPGe digitization parameters" — wrong header for the MWD module.
  • Copy-paste in .fcl: AbsorberFromSTLaBr.fcl uses process_name: HPGeAbsorberSpectrumShift (LaBr file, HPGe process name).
  • Author-acknowledged unfinished work: several @nil "Populate me!" placeholders (HPGeWaveformGenerationAndAnalysis.fcl, ROOTAnalysisDump.fcl), a makeADCPlot path that always throws "not yet implemented", and TODO blocks left in prolog.fcl ("TODOs before Pawel uploads").
  • Comment typo: "5. Ou1tputs" and "[BRAKCETS]" in HPGeWaveformGenerationAndAnalysis.fcl / ROOTAnalysisDump.fcl.

@FNALbuild

Copy link
Copy Markdown
Collaborator

📝 The HEAD of main has changed to 90a91d2. Tests are now out of date.

@YongyiBWu YongyiBWu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see any direct issue with the proposed changes

@AndrewEdmonds11 AndrewEdmonds11 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. There are several files which just have whitespace changes, which would be good to clean up before merging. There are some very useful modules in here that would be good for generating STM mock datasets: PhotonGun, HPGeWaveformsFromStepPoints etc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it's only whitespace changes. I think this file also got renamed at some point so might cause a merge issue...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll merge develop again before I merge, but I have a quick question - for the whitespace changes, what do you mean by "clean up before merging"? It took a while to be able to commit as the pre-commit action I had blocked the git commit before these were all changed, and I am unsure of how to disable this so I can re-commit the files with the whitespaces addressed

Comment thread STMMC/src/HPGeWaveformsFromStepPointMCs_module.cc Outdated
@PawelPlesniak

Copy link
Copy Markdown
Author

Responses to AI review

🔴 Bugs (likely functional problems)

1. SimParticleDump_module.cc — empty-file counter never resets, and threshold check is wrong

Because the empty branches return immediately, the threshold throw is only ever reached on a non-empty event — the exact opposite of intended.

This has been extended to check that the threshold is exceeded before checking that the event data products are not empty.

The counter is also never reset to 0 on success in the early-return paths. Compare with SimParticleAndVDBacktrace / VirtualDetectorTree, which check the threshold before returning.

This has been updated to unify the logic

The author also flags this file as unvalidated: // TODO before upload - validate that the code works.

Validation complete.

HPGeWaveformsFromStepPointMCs_module.cc — digitize() clipping logic is inverted

This review is incomplete, and does not understand the physics of what is being simulated:

nothing prevents positive overflow

Worth double-checking the direction against the validation if (j > 1000) throw ... "ADC values too high!" on line 355.

Positive overflow will not be encountered, unless the user sets the pedestal too high. This will be left for the user to address

3. HPGeWaveformsFromStepPointMCs_module.cc — validation constant defaultMicrospillBufferLengthCount < 1 is unreachable

Fair point, corrected.

4. PhotonGun_module.cc — new energy check rejects valid low-energy photons

Corrected.

5. HPGeWaveformGenerationAndAnalysis.fcl — dangling data-product tag after module rename

Good catch!

🟠 Correctness / robustness concerns

6. Uninitialized counters read in summaries

Corrected.

7. SimParticleDump uses SimParticles.at(step.trackId())

Corrected

8. Hard-coded 320 MHz coupling

The user is made aware of the difference if fADC is set differently.

🟡 Minor / cleanup

Typo: unqiueEnabledVDsVec in CountVirtualDetectorHits_module.cc, and duplicate ;;

Corrected

Debug leftovers

Removed

Copy-paste in .fcl

Corrected

Author-acknowledged unfinished work

This is actually for the user to populate.

Comment typo:

Addressed where relevant

@PawelPlesniak

Copy link
Copy Markdown
Author

I will wait for the build to be completed, review any issues should they arise, and await clarification on what the whitespace changes should be

@oksuzian

oksuzian commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

@FNALbuild run build test

@FNALbuild

Copy link
Copy Markdown
Collaborator

⌛ The following tests have been triggered for 265298f: build (Build queue - API unavailable)

@FNALbuild

Copy link
Copy Markdown
Collaborator

☔ The build is failing at 265298f.

scons: *** [build/al9-prof-e29-p103/Offline/tmp/STMMC/src/HPGeWaveformsFromStepPointMCs_module.os] Error 1
scons: *** [build/al9-prof-e29-p103/Offline/tmp/STMMC/src/SimParticleDump_module.os] Error 1
Test Result Details
test with Command did not list any other PRs to include
merge Merged 265298f at f408474
build (prof) Log file.
ceSimReco 〰️ Log file.
g4test_03MT 〰️ Log file.
transportOnly 〰️ Log file.
POT 〰️ Log file.
g4study 〰️ Log file.
cosmicSimReco 〰️ Log file.
cosmicOffSpill 〰️ Log file.
ceSteps 〰️ Log file.
ceDigi 〰️ Log file.
muDauSteps 〰️ Log file.
ceMix 〰️ Log file.
rootOverlaps 〰️ Log file.
g4surfaceCheck 〰️ Log file.
trigger Log file.
check_cmake 〰️ Log file.
FIXME, TODO ➡️ TODO (6) FIXME (1) in 19 files
clang-tidy ➡️ 35 errors 255 warnings
whitespace check no whitespace errors found

N.B. These results were obtained from a build of this Pull Request at 265298f after being merged into the base branch at f408474.

For more information, please check the job page here.
Build artifacts are deleted after 5 days. If this is not desired, select Keep this build forever on the job page.

@PawelPlesniak

Copy link
Copy Markdown
Author

@FNALbuild run build test

@FNALbuild

Copy link
Copy Markdown
Collaborator

⌛ The following tests have been triggered for 3cedaf7: build (Build queue - API unavailable)

@FNALbuild

Copy link
Copy Markdown
Collaborator

☀️ The build tests passed at 3cedaf7.

Test Result Details
test with Command did not list any other PRs to include
merge Merged 3cedaf7 at f408474
build (prof) Log file. Build time: 08 min 47 sec
ceSimReco Log file.
g4test_03MT Log file.
transportOnly Log file.
POT Log file.
g4study Log file.
cosmicSimReco Log file.
cosmicOffSpill Log file.
ceSteps Log file.
ceDigi Log file.
muDauSteps Log file.
ceMix Log file.
rootOverlaps Log file.
g4surfaceCheck Log file.
trigger Log file.
check_cmake Log file.
FIXME, TODO ➡️ TODO (6) FIXME (1) in 19 files
clang-tidy ➡️ 28 errors 264 warnings
whitespace check no whitespace errors found

N.B. These results were obtained from a build of this Pull Request at 3cedaf7 after being merged into the base branch at f408474.

For more information, please check the job page here.
Build artifacts are deleted after 5 days. If this is not desired, select Keep this build forever on the job page.

@oksuzian oksuzian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review Summary

Reviewed at head 3cedaf72

Decision

  • 🔴 request changes

Scope understood

  • STM simulation/analysis rework: HPGe waveform generation (preamplifier response, microspill carry-over, event-time accounting), MWD instrumentation, two new SimParticle provenance-dump analyzers, six new/rewritten STMMC driver fcls, and a full rewrite of STMMC/fcl/prolog.fcl.
  • A data-product instance rename in STMResamplingProducer ("""virtualdetector") paired with an open companion PR, Mu2e/Production#552.
  • Carried along: whitespace-only reformatting across DAQ/, BTrkLegacy/, TrkReco/, RecoDataProducts/, STMReco/ (pre-commit fallout, per the PR body).

Re-review accounting (prior review posted 2026-07-09 at head 74637245; delta 74637245..3cedaf72 = 8 files)

Prior finding Status
1. SimParticleDump empty-file counter / threshold 🟢 FIXED in e500ede0, verified
2. digitize() one-sided clipping 🟠 UNADDRESSED — carried forward (F4)
3. defaultMicrospillBufferLengthCount < 1 unreachable 🟢 FIXED (right variable now checked) / ⚪ message still wrong (F10)
4. PhotonGun epsilon() energy guard 🟢 FIXED, verified
5. Dangling PHHPGe in keep 🟢 FIXED, verified
6. Uninitialized counters 🟢 FIXED, verified
7. SimParticles.at(...) 🟢 FIXED in SimParticleDump; ⚪ still present in VirtualDetectorTree (pre-existing)
8. Hard-coded 320 MHz coupling 🟢 no longer silent — now throws (F14 notes the cadence)
9. makeADCplot / waveform_time // TODO - REMOVE ME! 🟢 FIXED in ddcc9c38/265298fc, verified absent at head
10. MWD banner, Ou1tputs, LaBr process_name 🟢 FIXED, verified
11. unqiueEnabledVDsVec, [BRAKCETS], @nil "Populate me!" 🟠 PARTIAL — carried forward (F9, F11)

Other reviewers: @YongyiBWu approved (2026-07-19, no items). @AndrewEdmonds11 approved (2026-08-03) with one request — clean up the whitespace-only files before merging — still open (see PR hygiene). His PlotSTMWaveformDigis comment was addressed by ddcc9c38/265298fc.


Findings

  1. 🔴 [S0] Offline/STMMC/fcl/prolog.fcl includes a Production/ file and cannot resolve against Production main

    • Evidence: STMMC/fcl/prolog.fcl:6 adds #include "Production/JobConfig/pileup/STM/prolog.fcl". The new tables then dereference @local::STMSimDataProducts.Stage1.CompressedOutput.StepPointMCs, ...Stage2.CompressedOutput.StepPointMCs.Virtualdetector, and @local::STMPileup.ResamplingProducer.VirtualDetectorID. STMSimDataProducts does not exist in Production main — it is introduced only by Mu2e/Production#552 (still OPEN, head 79419ad3, where it appears at JobConfig/pileup/STM/prolog.fcl:15 and STMPileup.ResamplingProducer at line 245). A code search over Mu2e/Offline for Production/JobConfig in .fcl returns STMMC/fcl/prolog.fcl plus only */test/*.fcl driver files — no other Offline prolog depends on Production.
    • Impact: two problems. (a) Layering inversion: Offline is the base layer; making an Offline prolog unparseable without Production on FHICL_FILE_PATH breaks Offline-standalone use and inverts the dependency for every consumer of Offline/STMMC/fcl/prolog.fcl. (b) Merge-order landmine: Production main's JobConfig/pileup/STM/BeamToVD.fcl still #includes Offline/STMMC/fcl/prolog.fcl, so merging this PR before #552 makes the live STM pileup drivers fail to parse. Offline CI cannot catch this — no validation job in the suite (ceSimReco, ceDigi, ceMix, g4test_03MT, …) runs any STMMC fcl, which is why mu2e/buildtest is green at 3cedaf72.
    • Suggested fix: move the new DataProducts table into Production's STM prolog (that is where STMSimDataProducts already lives) and delete the #include "Production/..." line from the Offline prolog. If any of it must stay in Offline, inline the literals. Either way, state the required merge order in both PR bodies and land Production#552 first.
  2. 🔴 [S0] STMResamplingProducer instance rename breaks three Production drivers the companion PR does not touch

    • Evidence: STMMC/src/STMResamplingProducer_module.cc:47,66 change produces<StepPointMCCollection>()produces<StepPointMCCollection>("virtualdetector") and event.put(std::move(outputStepPointMCs), "virtualdetector"). Production#552 updates BeamToVD.fcl and BeamToVD1809.fcl only. Still on Production main and absent from #552's 16-file list: JobConfig/pileup/STM/BeamTo2VD.fcl:14, BeamTo2VD1809.fcl:14, NeutralsTo2VD.fcl:17, each of which hardcodes physics.producers.compressDetStepMCsSTM116.stepPointMCTags : ["extractVirtualDetectorSteps116"] — a bare label, i.e. instance "". Compression/src/CompressDetStepMCs_module.cc:618 resolves those with event.getValidHandle<StepPointMCCollection>(i_tag), which throws ProductNotFound on a miss.
    • Impact: BeamTo2VD, BeamTo2VD1809 and NeutralsTo2VD abort at the first event once this lands. These are the two-VD variants used for the VD116 studies.
    • Suggested fix: add "extractVirtualDetectorSteps116:virtualdetector" to those three fcls in Production#552 (they are the only remaining bare-instance consumers).
  3. 🟠 [S1] HPGe microspill charge carry-over is snapshotted before the deposits, so the buffer is permanently zero

    • Evidence: STMMC/src/HPGeWaveformsFromStepPointMCs_module.cc:288-292 now runs at the top of produce(), ahead of depositCharge() at lines 308-319:
      _chargeCollected.clear();
      _chargeCollected.assign(_chargeCarryOver.begin(), _chargeCarryOver.end());
      _chargeCollected.insert(_chargeCollected.end(), nADCs, 0);   // tail := zeros
      _chargeCarryOver.clear();
      _chargeCarryOver.assign(_chargeCollected.begin() + nADCs, _chargeCollected.end()); // reads the zeros
      The pre-PR code ran this block at the end of produce() and in the opposite order — _chargeCarryOver was assigned from the tail first (after the deposits had landed there), and only then was _chargeCollected rebuilt. Moving the block without swapping the two halves reversed the data dependency. Concretely, with nADCs_init = 543 and microspillBufferLengthCount = 2: _chargeCollected = 543 old-carry-over values + 543 fresh zeros, and _chargeCarryOver is then assigned exactly that zero tail. It is all zeros for every event after the first.
    • Impact: depositCharge() writes late deposits into _chargeCollected[i] for i >= nADCs (steps admitted by the step.time() > microspillBufferLengthCount * micropulseTime cut at line 374), and those are wiped at the start of the next event. The delayed component that microspillBufferLengthCount exists for — "charge deposits that are allocated to this event but happen after the microspill ends e.g. 844keV", per the member comment at line 150 — is silently discarded. This is a regression against main, and it is invisible: no exception, no counter, just missing charge.
    • Suggested fix: restore the original ordering — assign _chargeCarryOver from the tail of _chargeCollected after the deposit loops, then rebuild _chargeCollected for the next event. If the block must stay at the top of produce() (e.g. for the resetEventNumber interaction at 281-285), split it: rebuild _chargeCollected at the top, snapshot _chargeCarryOver at the bottom.
  4. 🟠 [S1] digitize() clamps only the negative rail; positive overflow is undefined behaviour — carried over from the 2026-07-09 review, unaddressed

    • Evidence: HPGeWaveformsFromStepPointMCs_module.cc:530
      _adcs[i] = (ADC > ADCMax) ? static_cast<int16_t>(std::round(ADC)) : ADCMax;
      with ADCMax = static_cast<int16_t>((-1 * std::pow(2,15)) + 1) = -32767 (line 111). Values below -32767 are floored; nothing bounds ADC above +32767. A double outside the int16_t range converted by static_cast is UB, so the result is whatever the target ISA's conversion does. The only downstream guard is if (j > 1000) throw cet::exception("LogicError", "ADC values too high!") at line 337 — it runs after the bad cast, and a hard-coded 1000 that aborts the job is itself a debug artefact rather than a saturation model.
    • Impact: with NoiseSD non-zero the baseline sits near 0 and positive excursions are reachable; the failure mode is either a wrapped ADC value written into the digi or a job abort with an unhelpful message.
    • Suggested fix: _adcs[i] = static_cast<int16_t>(std::round(std::clamp(ADC, static_cast<double>(ADCMax), static_cast<double>(std::numeric_limits<int16_t>::max())))); and replace the > 1000 guard with a saturation counter reported in endJob().
  5. 🟠 [S1] Efficiency.PhotonEnergy : @nil makes all three Absorber drivers non-runnable, including one that worked before this PR

    • Evidence: the prolog rewrite changes Efficiency.PhotonEnergy from 1.809 to @nil (STMMC/fcl/prolog.fcl, Efficiency table). Three drivers consume it unconditionally: Absorber.fcl:31, AbsorberFromSTHPGe.fcl:34, AbsorberFromSTLaBr.fcl:34, all E : @local::Efficiency.PhotonEnergy. EventGenerator/src/PhotonGun_module.cc:47 declares fhicl::Atom<double> E — required, no default — and line 69 throws on E <= 0.0.
    • Impact: @nil erases the key, so the reference does not deliver a value and the required atom fails validation at module construction. Absorber.fcl ran before this PR (PhotonEnergy : 1.809); after it, none of the three start. Note the same rewrite also (correctly) repairs Absorber.fcl's module_type : MakeVirtualDetectorTree, a module that does not exist in the repo — so this file goes from broken-one-way to broken-another-way.
    • Suggested fix: keep PhotonEnergy : 1.809 in the prolog and let a driver override it. (The LaBrDigitization table's @nil entries are fine — nothing references them.)
  6. 🟡 [S2] Seven new members in STMMovingWindowDeconvolution are declared and never read

    • Evidence: STMReco/src/STMMovingWindowDeconvolution_module.cc:118-125 adds _isFirstWaveform, _last_ADC, _last_deconvolved, _deconvolved_history, _differentiated_history, _last_average, and (line 153) waveform_time_microspill_frame. Each string occurs exactly once in the file — the declaration. The two std::deques carry the comment "Rolling history buffers to bridge the gap between chunks", i.e. they were added for a chunk-boundary continuity fix that is not in the diff. <deque> is also never included; it compiles only via a transitive boost/stdlib include.
    • Impact: dead state that reads as an implemented feature. A future reader will assume the chunk gap is handled.
    • Suggested fix: delete them, or land the chunk-bridging logic they belong to. (Does not gate the decision.)
  7. 🟡 [S2] SimParticleAndVDBacktrace copies the whole StepPointMC collection per particle and per ancestor

    • Evidence: STMMC/src/SimParticleAndVDBacktrace_module.cc:248 const std::vector<StepPointMC> StepPointMCs = *stepHandle; copies the product; then addToTree, addParentToTree and traceSteps all take const std::vector<StepPointMC> steps by value (declarations at lines 75-77, definitions at 158, 178, 213). addToTree recurses up the parent chain (lines 204-210), copying the full collection at every hop, and traceSteps linearly scans it each time (line 160).
    • Impact: O(N_particles x chain_depth) full-vector copies plus an O(N_steps) scan each, i.e. quadratic in a VD collection that can hold thousands of steps. This is a new module, so the cost is entirely introduced here.
    • Suggested fix: const std::vector<StepPointMC>& on all three signatures and auto const& StepPointMCs = *stepHandle; in analyze.
  8. 🟡 [S2] Per-microspill sample budget averages 543.6 ticks where the clock gives 542.4 — the concatenated timeline stretches

    • Evidence: HPGeWaveformsFromStepPointMCs_module.cc:273-278
      nADCs = nADCs_init;                       // 543 at 320 MHz (line 197-199)
      eventTimeBuffer = eventId % 5;
      if (!(eventTimeBuffer == 0 || eventTimeBuffer == 3)) nADCs++;
      and eventTime += nADCs at line 361. Over any 5 consecutive microspills that emits 543*5 + 3 = 2718 ticks. The physical budget is 5 * micropulseTime / tADC = 5 * 1695 ns / 3.125 ns = 2712 ticks. The +1 at line 197 ((micropulseTime/tADC) + 1, documented as "increase the number by one due to truncation") is a per-microspill ceiling, which is right for sizing one buffer but wrong as the base of a running total.
    • Impact: 6 extra ticks (18.75 ns) per 5 microspills, ~0.22 %. Over the nMicrospillsPerSpill : 31858 concatenation configured in the prolog that is roughly 24 µs of accumulated offset by the end of a macrospill, which lands directly on the MWD peak times.
    • Suggested fix: base nADCs_init on the floor (542) with +1 on 2 of every 5 microspills, or drive the pattern from an exact accumulator over micropulseTime/tADC. Please confirm the intended tick budget against the DAQ spec — the old code (+= nADCs + 1 when eventId%5==0 || eventId%3==0) had a different, also non-exact, pattern, so this is a behaviour change either way.
  9. 🟡 [S2] Committed drivers still carry @nil "Populate me!" placeholders and one dangling module_typecarried over, partially addressed

    • Evidence: HPGeWaveformGenerationAndAnalysis.fcl:90 trigger_paths : @nil and :99 SelectEvents: @nil; ROOTAnalysisDump.fcl:103 o1 : ["HPGeEDeps"] # Populate me! and :107; ROOTAnalysisDump.fcl:86 sets module_type : MWDTree and lines 93/96/99 set module_type : @nil. A code search over Mu2e/Offline for MWDTree returns nothing — the module does not exist (STMMC/src/ has PHTree_module.cc and HPGeTree_module.cc). These are inert only because they sit off the active paths.
    • Impact: HPGeWaveformGenerationAndAnalysis.fcl cannot be run as shipped; ROOTAnalysisDump.fcl ships a reference to a non-existent module. The PR body states this file will not be brought up to the current analysis framework, so it will stay in this state.
    • Suggested fix: give both drivers a runnable default path (trigger_paths : [digitization_path_ZS], SelectEvents : [digitization_path_ZS]), delete the @nil/MWDTree analyzer blocks, or relocate these to STMMC/test/ where non-runnable scaffolding is expected.
  10. ⚪ [S3] microspillBufferLengthCount guard message contradicts its own check — residual of prior finding 3

    • HPGeWaveformsFromStepPointMCs_module.cc:192-193: if (microspillBufferLengthCount < 2) throw cet::exception("RANGE", "microspillBufferLengthCount has to be at least 1!\n");. The variable swap was the substance of the fix and is correct, but the bound is 2 (line 202 sizes _chargeCarryOver as nADCs * (count - 1)), so a user who sets 1 is told 1 is allowed. Change the message to "at least 2".
  11. ⚪ [S3] Carried-over typos still present

    • unqiueEnabledVDsVecAnalyses/src/CountVirtualDetectorHits_module.cc:55,60 (the duplicate ;; on that line was fixed).
    • [BRAKCETS]STMMC/fcl/HPGeWaveformGenerationAndAnalysis.fcl:10 and STMMC/fcl/ROOTAnalysisDump.fcl:10.
    • New: mf::LogInfo log("HPGeWaevfrmsFromStepPointMCs")HPGeWaveformsFromStepPointMCs_module.cc:536, misspelled message-facility category.
  12. ⚪ [S3] Dead and duplicated configuration state

    • STMMC/src/VirtualDetectorTree_module.cc:64 adds uint simParticleIdKey = 0; — declared, never used (the tree fills simParticleId, a different member).
    • STMMC/fcl/prolog.fcl defines ComponentPositions.ST and Efficiency.ST with identical values (-3904.0, 0.0, 627.0) in the same file — one should reference the other.
    • DigitizationTester.HPGe.delta{x,y,z} are annotated # MeV/c, but PhotonGun::Config (lines 44-46) documents deltax/y/z as position differences used to build a direction; FirePhotonToDetector.fcl never reads them, so nothing catches the mismatch.
    • Please confirm Efficiency.FromSTToDet.LaBr.z : 40264.5: ST.z + 40264.5 = 40891.5, whereas ComponentPositions.LaBr.z (added in this PR) is 40404. The HPGe pair is self-consistent (627 + 40072.1 = 40699.1 = crystalCentreZ in the module), so the LaBr offset looks like it targets a different reference point.
  13. ⚪ [S3] Includes and duplication in the new/modified modules

    • SimParticleAndVDBacktrace_module.cc uses std::find (105, 163), std::fill (172, 174) and std::distance (165) but does not #include <algorithm>; it compiles transitively.
    • Analyses/src/CountVirtualDetectorHits_module.cc:6 and HPGeWaveformsFromStepPointMCs_module.cc:12 keep #include <bits/stdc++.h> — a GCC-internal header, non-portable and the opposite of the "include only headers actually needed" rule. Pre-existing in both, so not gating, but CountVirtualDetectorHits is being edited here anyway and now only needs <algorithm>, <unordered_set>, <vector>.
    • SimParticleAndVDBacktrace::addToTree (178-198) and addParentToTree (213-235) duplicate ~15 lines verbatim. The comment at line 203 — "Require separate definitions of addParentToTree because of data accessors" — no longer applies now that both take const art::Ptr<SimParticle>&; they can be one function.
  14. ⚪ [S3] override and check cadence in HPGeWaveformsFromStepPointMCs

    • beginJob() (line 78) and the newly added endJob() (line 83) are declared without override; the base signatures match today, so they are called, but a base-class change would silently orphan them.
    • The fADC == 320 guard at lines 271-272 is a constructor-time invariant evaluated on every event. Move it next to the tADC computation at line 188.
  15. ⚪ [S3] depositCharge() writes ~1000 dead values per StepPointMC

    • Lines 471-472 replaced _charge[tIndex] = N_ehPairs; with a loop that fills the plateau across the entire remaining _charge buffer. Only [tIndexStart, tIndex) is read by the _chargeCollected update at 480-481, and std::fill(_charge.begin(), _charge.end(), 0) at line 484 wipes the rest. Writing just _charge[tIndex] = _charge[tIndex - 1] preserves the (intended) continuity change at a thousandth of the cost.

Observed, pre-existing, not gating

  • HPGeWaveformsFromStepPointMCs::addNoise() (lines 519-520) constructs std::default_random_engine _randomGen; fresh inside the per-event call with the default seed, so every microspill receives the identical noise realization, and it bypasses SeedService entirely. Not introduced by this PR, but this PR reworks the charge path that feeds it (decayCharge now emits currentIntegrated), so the noise model is worth fixing in the same campaign: hoist to a member seeded via art::ServiceHandle<art::RandomNumberGenerator> / SeedService.
  • VirtualDetectorTree_module.cc:113 still uses SimParticles.at(step.trackId()), which throws std::out_of_range rather than a cet::exception with a useful category. SimParticleDump was fixed for exactly this in e500ede0; the sibling module was not.

Verified — no action needed

  • 🟢 Build-system parity checked in both systems. STMMC/CMakeLists.txt registers both new plugins (SimParticleDump at 60-66, SimParticleAndVDBacktrace at 68-74) with LIBRARIES REG matching the sibling VirtualDetectorTree entry (art_root_io::TFileService_service, Offline::GlobalConstantsService, Offline::MCDataProducts) — correct for their include sets; VirtualDetectorId comes in transitively via MCDataProducts, same as the existing ShiftVirtualDetectorStepPointMCs and VirtualDetectorTree plugins. STMMC/src/SConscript is a generic helper.make_plugins([...]) glob over *_module.cc, so it needs no per-module edit. No configure_file inputs are involved; install_fhicl(SUBDIRS fcl SUBDIRNAME Offline/STMMC/fcl) (line 143) already covers the six new fcl files. No un-mirrored plugin and no missing LIBRARIES.
  • 🟢 Absorber.fcl previously specified module_type : MakeVirtualDetectorTree — a module that exists nowhere in the repo — plus a VirtualDetectorId : 101 key. This PR corrects both against VirtualDetectorTree::Config (which declares only StepPointMCsTag, SimParticlemvTag, consecutiveEmptyFileThreshold).
  • 🟢 CountVirtualDetectorHits order change is intentional and safe: the old std::set sorted the IDs, the new path preserves config order, and Production#552 supplies an unsorted list ([10, 13, 15, 86, 115, 100, 101, 88, 89, 90]) whose report order now matches the config.
  • 🟢 HPGeWaveformGenerationAndAnalysis.fcl module labels resolve: STMZeroSuppression and ConcatenateDigitizedWaveforms are both registered plugins; keep mu2e::STMPHDigis_MWDHPGe_*_HPGeReco now matches the MWDHPGe producer label.
  • 🟢 mu2e/buildtest is green at 3cedaf72 (2026-08-04T06:53Z) along with all 14 validation jobs — note none of them exercise STMMC, which is why findings 1, 2, 5 and 9 are invisible to CI.

Validation check

  • Build/tests run: yes, by Mu2e CI at the reviewed head — mu2e/buildtest success plus ceSimReco, ceDigi, ceMix, ceSteps, cosmicSimReco, cosmicOffSpill, g4test_03MT, g4study, transportOnly, POT, muDauSteps all rc 0. No STM fcl is exercised by any of them, and the PR supplies no STM-specific validation evidence.
  • Config contract check: fail — findings 1 (unresolvable @local:: references against Production main), 5 (@nil for a required Atom<double>), 9 (@nil paths, non-existent MWDTree).
  • Cross-repo consistency: fail — finding 2 (three Production drivers not covered by Production#552) and finding 1 (hard, undeclared merge-order dependency on Production#552 in both directions).

Residual risk

  • Findings 1 and 2 are release-blocking in the ordering sense: whichever of #1875 / Production#552 merges first leaves the STM pileup chain broken until the other lands, and #552 as written does not cover BeamTo2VD/BeamTo2VD1809/NeutralsTo2VD at all.
  • Finding 3 is a silent physics regression. Any HPGe waveform sample already produced from this branch under-counts the delayed (844 keV-class) charge, and nothing in the job output signals it.
  • Finding 8 changes the microspill time base relative to main; MWD peak times from before and after this PR are not directly comparable.
  • The whitespace-only churn (DAQ/src/STMBinaryDigisFromFragments_module.cc alone is +173/-173) inflates the diff and, per @AndrewEdmonds11's inline note, risks a merge conflict with a rename of that file. The physics changes are hard to isolate for review or for git bisect.

Author follow-ups

  1. Decide the layering for STMMC/fcl/prolog.fcl: move the DataProducts table into Production's STM prolog and drop #include "Production/JobConfig/pileup/STM/prolog.fcl" from Offline. (F1)
  2. Add :virtualdetector to the stepPointMCTags in Production BeamTo2VD.fcl, BeamTo2VD1809.fcl and NeutralsTo2VD.fcl in PR #552, and record the required Offline/Production merge order in both PR bodies. (F2)
  3. Restore the carry-over ordering in produce() so _chargeCarryOver is snapshotted after depositCharge(). Please add a short before/after check — e.g. a single microspill with a step at t > 1695 ns — showing the charge appears in the next event's waveform. (F3)
  4. Clamp digitize() on both rails and replace the > 1000 abort with a saturation counter. (F4)
  5. Restore Efficiency.PhotonEnergy : 1.809 in the prolog so the three Absorber drivers run. (F5)
  6. Either delete the seven unused MWD members or land the chunk-bridging logic they were added for. (F6)
  7. Pass the StepPointMC collection by const reference throughout SimParticleAndVDBacktrace. (F7)
  8. Confirm the intended ticks-per-microspill budget against the DAQ spec and reconcile the 543 + 3-in-5 pattern with 1695/3.125 = 542.4. (F8)
  9. Give HPGeWaveformGenerationAndAnalysis.fcl and ROOTAnalysisDump.fcl runnable defaults (or move them to STMMC/test/), and drop the MWDTree / @nil analyzer blocks. (F9)
  10. Best practice reminder: please split the whitespace-only reformatting of DAQ/, BTrkLegacy/, TrkReco/ and RecoDataProducts/ into a separate PR so the STM physics changes stand alone — this also addresses @AndrewEdmonds11's outstanding request. Answering your question about how: for files where only whitespace changed, git checkout main -- <path> restores them; to get past the hook for that commit use git commit --no-verify (or SKIP=<hook-id> git commit for a single hook). No need to disable the config permanently.
  11. Your open question — "why was STMMWDDigi renamed to STMPHDigi?" — is still unanswered, and the PR leaves the naming inconsistent: the prolog now exposes STMMWDDigiTag (STMMC/fcl/prolog.fcl, STMMCAnalysis.PH.{HPGe,LaBr}) while the data-product class is still STMPHDigi (keep mu2e::STMPHDigis_MWDHPGe_*_HPGeReco). Worth settling with the STMReco authors before merge so the config key and the product name agree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants