Skip to content

Iss2035 add LUT method tracking to TSTrackProducer#2070

Open
kvarnla wants to merge 21 commits into
trunkfrom
iss2035
Open

Iss2035 add LUT method tracking to TSTrackProducer#2070
kvarnla wants to merge 21 commits into
trunkfrom
iss2035

Conversation

@kvarnla

@kvarnla kvarnla commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

I am updating ldmx-sw, here are the details.​​

As part of my bachelor thesis project, I have implemented a method of TS track-making using a lookup table to accommodate potential misalignment. To do this, a boolean variable "lut_tracking" has been added to TrigScint/src/TrigScint/TrigScintTrackProducer, so that this method and the (currently existing) method using max_delta can be 'switched between.' It reads a LUT file, a text file, in order to realize the new method; the LUT file is constructed using two new analyzers as described below. The LUT file is a list of cluster combinations from the three pads which appear "often enough" to be considered as originating from beam electrons. ​ ​

I have added the following files: ​

  • ClusterTripletMaker.cxx and ClusterTripletMaker.h : analyzer to write the clusters in pads 1, 2, and 3 to a text file ("clusters.txt"). ​
  • PatternLUTMaker.cxx and PatternLUTMaker.h : analyzer to take the "clusters.txt" as input and output a LUT text file ("LUT.txt"). It computes the vertical distances between cluster centroids in pads 1 and 2, and between clusters in pads 2 and 3, and groups them by these combinations ("patterns"). The configurable "lut_threshold" is the minimum frequency that a such combination must appear in order to be written to the LUT.txt file. I have set it to default 0.0008 since this returns the highest track-making efficiency with the lowest rate of fake tracks for sets of <1 million events. ​
  • runClusterstxt.py and runLUTana.py : exampleConfigs for the respective analyzers above. ​

I have changed the following files: ​

  • TrigScintTrackProducer.cxx and TrigScintTrackProducer.h : to introduce lut_tracking and another variable (lut_file) to specify the LUT file. ​
  • trig_scint.py : to include the new variables in 'TrigScintTrackProducer' and added definitions for both new analyzers. ​(Additionally: I believe TrigScintTrackProducer was defined twice in trig_scint.py for some reason. I have removed one block).
  • runLUTtracking.py : an exampleConfig to demonstrate configuring TrigScintTrackProducer to run both lut-method tracking and max_delta definition tracking. ​

What are the issues that this addresses?​​

This resolves issue #2035.​

Check List​

  • I successfully compiled ldmx-sw with my developments.​
  • I read, understood and follow the coding rules.​
  • I ran my developments and the following shows that they are successful.​
​ ​

Excerpt of runClusterstxt.py output, which utilizes ClusterTripletMaker.cxx to write the following clusters.txt file:

image

The columns here refer to event number, pad 1 cluster centroid, pad 2 cluster centroid, and pad 3 cluster centroid.

Excerpt of runLUTana.py output, which takes the runClusterstxt.py output and uses PatternLUTMaker.cxx to write the following LUT.txt:​

image

The columns here refer to pad 1 cluster, pad 2 cluster, and pad 3 cluster for those rows in clusters.txt which pass the LUT threshold when grouped into patterns in PatternLUTMaker.

Here is track-making as performed in runLUTtracking.py for both lut-method tracking and current max_delta method tracking. The number of tracks made by lut-method tracking is dependent on the LUT threshold, here set at 0.0008:

For the max_delta definition tracking, as currently implemented in TrigScintTrackProducer:
image

For lut-method tracking, as proposed here (for a lut_threshold of 0.0008):
image

@kvarnla kvarnla requested review from bryngemark and cjbarton151 June 6, 2026 19:44
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Validation Results

Some validation samples failed! ❌

Sample Status
cascade_history ✅ PASS
deep_ecal_gun ✅ PASS
eat_signal ✅ PASS
ecal_pn ✅ PASS
hcal ✅ PASS
inclusive ✅ PASS
it_pileup ✅ PASS
kaon_enhanced ✅ PASS
signal ✅ PASS
signal_target_al ✅ PASS
target_genie ✅ PASS
target_pn_lyso ✅ PASS
wab_lhe ✅ PASS
reduced_ldmx ❌ FAIL (211 histograms failed KS test) (artifact)
target_ti_en ❌ FAIL (386 histograms failed KS test) (artifact)

reduced_ldmx:

  • 211 plots failed the KS test against gold.
  • Text Differences Between Logs (258298 lines differ)
  • Log character count differs by 0% (gold=33510645, new=33428744); within tolerance
  • Timing anomaly for reduced_ldmx: new=214s vs gold=1881s (-88%, tolerance 10%)
  • Timing for reduced_ldmx: gold=1881s, new=214s

target_ti_en:

  • 386 plots failed the KS test against gold.
  • Text Differences Between Logs (6904 lines differ)
  • Log character count differs by 4%: gold=377322, new=392949
  • Timing for target_ti_en: gold=3247s, new=3282s
  • Timing within 1% of gold (tolerance 10%)

cascade_history:

  • Text Differences Between Logs (2 lines differ)
  • Timing for cascade_history: gold=6401s, new=6587s
  • Timing within 2% of gold (tolerance 10%)

deep_ecal_gun:

  • Text Differences Between Logs (26 lines differ)
  • Timing anomaly for deep_ecal_gun: new=3298s vs gold=3976s (-17%, tolerance 10%)
  • Timing for deep_ecal_gun: gold=3976s, new=3298s

eat_signal:

  • Timing for eat_signal: gold=1542s, new=1604s
  • Timing within 4% of gold (tolerance 10%)

ecal_pn:

  • Text Differences Between Logs (42 lines differ)
  • Timing for ecal_pn: gold=7360s, new=6952s
  • Timing within 5% of gold (tolerance 10%)

hcal:

  • Text Differences Between Logs (2 lines differ)
  • Timing for hcal: gold=854s, new=854s
  • Timing within 0% of gold (tolerance 10%)

inclusive:

  • Text Differences Between Logs (32 lines differ)
  • Log character count differs by 0% (gold=21025667, new=21025666); within tolerance
  • Timing for inclusive: gold=7065s, new=7061s
  • Timing within 0% of gold (tolerance 10%)

it_pileup:

  • Text Differences Between Logs (42 lines differ)
  • Timing for it_pileup: gold=580s, new=579s
  • Timing within 0% of gold (tolerance 10%)

kaon_enhanced:

  • Text Differences Between Logs (36 lines differ)
  • Timing regression for kaon_enhanced: new=3239s vs gold=2867s (+12%, tolerance 10%)
  • Timing for kaon_enhanced: gold=2867s, new=3239s

signal:

  • Text Differences Between Logs (32 lines differ)
  • Timing for signal: gold=1255s, new=1289s
  • Timing within 2% of gold (tolerance 10%)

signal_target_al:

  • Text Differences Between Logs (40 lines differ)
  • Timing regression for signal_target_al: new=1022s vs gold=653s (+56%, tolerance 10%)
  • Timing for signal_target_al: gold=653s, new=1022s

target_genie:

  • Text Differences Between Logs (4480 lines differ)
  • Timing regression for target_genie: new=7029s vs gold=5487s (+28%, tolerance 10%)
  • Timing for target_genie: gold=5487s, new=7029s

target_pn_lyso:

  • Text Differences Between Logs (38 lines differ)
  • Timing for target_pn_lyso: gold=6580s, new=6465s
  • Timing within 1% of gold (tolerance 10%)

wab_lhe:

  • Text Differences Between Logs (22 lines differ)
  • Timing for wab_lhe: gold=7412s, new=7513s
  • Timing within 1% of gold (tolerance 10%)

@cjbarton151

Copy link
Copy Markdown
Contributor

Hey Lucia - I thought I'd make a few intertwined, higher-level suggestions that wouldn't fit properly into the line-by-line code review.

Currently, your commit implements the LUT production in TrigScint/ClusterTripletMaker. I would argue that the LUT construction will probably be done once for each geometry*, and so it doesn't need to be integrated into the main bus. Maybe it should exist in Tools/ClusterTripletMaker and Tools/PatternLUTMaker and be called with its own dedicated config script, also stored in Tools. You can see Tools/HgcrocEmulator and the associated config script for an example on how to do this. Then, the LUT.txt files can be stored in TrigScint/data, each one with a slightly more descriptive name for each geometry and threshold e.g. LUT_v15_8gev_10000.txt or LUT_reducedv3_8gev_10000.txt, for events with a threshold of 1/10000.

*You might be wondering 'why would I only need to make one LUT for each geometry, when the probability threshold can be varied?' Well, I see an opportunity to streamline the functionality by including the probability of each combination as a 5th parameter in the LUT, then do the threshold cut once on process start in the TrigScintTrackProducer. But we can leave this task for another contributor. For now, I think that we probably won't be playing around with too many different thresholds; we'd probably just choose a seemingly optimal threshold for any given geometry and stick with it, so having one LUT.txt per geometry should in practice suffice.

@bryngemark may have different thoughts on the matter, so I think it would be useful to hear a second opinion before making large-scale changes to your pull request.

@bryngemark bryngemark 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 overall, most of my comments are on formatting etc. I have two general requests:

  1. add more comments explaining what the code does, either at the top of files/functions or as you go along, as needed
  2. condense vertical white space a bit. there is no need for blank lines between sets of variables etc, and it makes the code long and hard to read (you have to scroll). NOTE if this white space was added by the formatting script then nevermind

triplets.output_collection = "clusters.txt"

p.sequence = [
#*truth_hits,

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.

if you want these commented here as an indication that including them is optional, i suggest either

  • adding a comment saying something like "uncomment to make truth tables" (but i think you need some more stuff for that to work, right? you'd need to define truth_hits and use them as input collection to the later processors (at least digi))
  • adding a more elaborate setup which does all of the needed steps to use truth hits, and toggle with some boolean, doing {if [that boolean], define stuff, p.sequence.append(truth_hits), then digi etc}

from LDMX.Framework import ldmxcfg

p = ldmxcfg.Process("LUTmaker")
p.input_files = ["Clusters.root"] #necessary to define but unused, the real input file is defined below

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 see what @cjbarton151 means here: you're not really using ldmx-sw capabilities to manage ROOT tree input and output objects for this, but rather an auxiliary file structure where you have a messy input list and an ordered output list. So in that sense he is right that it's more of a tool and I think moving these files (stuff not reading/writing TS info from/to a tree) to Tools is a good idea. Reach out to one of us if this seems too complicated and we can discuss

Comment on lines +17 to +21
lut_tracks.delta_max = 1.0
lut_tracks.verbosity = 1
lut_tracks.lut_tracking = True
lut_tracks.lut_file = "LUT.txt"
lut_tracks.output_collection = "lutTracks"

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.

do these settings differ from the default ones specified in trig_scint.py? if not, i suggest omitting them

/**
* @file ClusterTripletMaker.h
* @brief Writes cluster combinations to text file
* @author Lucia Kvarnstrom, Lund University

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.

typo: ö ;)

std::vector<std::string> cluster_input_collections_;

// output text file
std::string output_collection_{"clusters.txt"};

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.

can you name it something else than collection since it's not going to be on a ROOT TTree ?

if (centroid >= vert_bar_start_idx_ &&
seed.getCentroidY() < cluster1.getCentroidY()) {
// impossible combination
if (verbose_ > 1) {

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.

remove verbosity check and promote logger level to warning

cluster1.getCentroidY() >
cluster2.getCentroidY())) { // impossible
if (verbose_ > 1) {
ldmx_log(debug)

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.

same here

Comment on lines +388 to +390
3 * max_delta_) &&
(track.getCentroid() <
vert_bar_start_idx_)) // for the horizontal bars

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.

Suggested change
3 * max_delta_) &&
(track.getCentroid() <
vert_bar_start_idx_)) // for the horizontal bars
3 * max_delta_) && (track.getCentroid() < vert_bar_start_idx_)) // for the horizontal bars

Comment on lines +427 to +432
if (((fabs((tracks_.at(idx)).getResidualX() -
(tracks_.at(idx_comp)).getResidualX())) <
0.01) // it should be equal
&&
(track.getCentroid() >=
vert_bar_start_idx_)) { // specific case for the vertical bars

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.

Suggested change
if (((fabs((tracks_.at(idx)).getResidualX() -
(tracks_.at(idx_comp)).getResidualX())) <
0.01) // it should be equal
&&
(track.getCentroid() >=
vert_bar_start_idx_)) { // specific case for the vertical bars
if (((fabs((tracks_.at(idx)).getResidualX() -
(tracks_.at(idx_comp)).getResidualX())) < 0.01) // should be equal
&& (track.getCentroid() >= vert_bar_start_idx_)) { // specific case for the vertical bars

Comment on lines +755 to +756
sx = fabs(x1 - x2) /
2; // rely on x precision being one single pad width

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.

Suggested change
sx = fabs(x1 - x2) /
2; // rely on x precision being one single pad width
sx = fabs(x1 - x2) / 2; // rely on x precision being one single pad width

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This I think was the clang tidy, the way out of it is to comment above the line

Suggested change
sx = fabs(x1 - x2) /
2; // rely on x precision being one single pad width
// rely on x precision being one single pad width
sx = fabs(x1 - x2) / 2;

@bryngemark bryngemark added the trigscint topics pertaining to trigger scintillator simulation and analysis development label Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

trigscint topics pertaining to trigger scintillator simulation and analysis development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants