From 34cf39f2f5cdde217cabe53c9c32d393ac83ad00 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 20 Jun 2025 14:35:13 +0200 Subject: [PATCH 01/99] Adapt geofile info following geofile updates We have new geofiles for all configurations, testbeam and TI18, with the new Scifi model and adding the 2023 emulsion target run 5 geo calibration. --- analysis/tools/geo_paths.csv | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/analysis/tools/geo_paths.csv b/analysis/tools/geo_paths.csv index f682f2bbd4..0682dc9daa 100644 --- a/analysis/tools/geo_paths.csv +++ b/analysis/tools/geo_paths.csv @@ -1,6 +1,7 @@ min_run_number,max_run_number,path -0,7356,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2023/geofile_sndlhc_TI18_V4_2023.root +0,5422,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2022/geofile_sndlhc_TI18_V4_2022.root +5482,7356,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2023/geofile_sndlhc_TI18_V3_2023.root 7357,10422,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2024/geofile_sndlhc_TI18_V12_2024.root 100238,100679,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/commissioning/testbeam_June2023_H8/geofile_sndlhc_H8_2023_3walls.root -100841,100953,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/commissioning/testbeam_24/geofile_sndlhc_H4_2024_W_2walls_v2.root +100841,100953,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/commissioning/testbeam_24/geofile_sndlhc_H4_2024_W_2walls.root 100954,100985,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/commissioning/testbeam_24/geofile_sndlhc_H4_2024_Fe_1wall.root From 23c60b1860490440b9d9224e929d81a5d9d67570 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 20 Jun 2025 14:40:51 +0200 Subject: [PATCH 02/99] Modify geofiles using updated base files This actually makes life easier as now all filenames are consistent and we removed the versions featuring date/month/year. --- shipLHC/modifyGeoFileDict.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index 9bc7a66858..33ed512f71 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -9,17 +9,18 @@ theClient = client.FileSystem('root://eospublic.cern.ch') commonPath = "/eos/experiment/sndlhc/convertedData/physics/" -'''supportedGeoFiles = ["geofile_sndlhc_TI18_V1_06July2022.root","geofile_sndlhc_TI18_V2_12July2022.root","geofile_sndlhc_TI18_V3_08August2022.root", - "geofile_sndlhc_TI18_V4_10August2022.root","geofile_sndlhc_TI18_V5_14August2022.root","geofile_sndlhc_TI18_V6_08October2022.root", - "geofile_sndlhc_TI18_V7_22November2022.root"] -''' -supportedGeoFiles = {"geofile_sndlhc_TI18_V7_22November2022.root":commonPath+"2022/", - "geofile_sndlhc_TI18_V0_2024.root":commonPath+"2024/"} +supportedGeoFiles = {} +supported_years = [2022, 2023, 2024] +for year in supported_years: + supportedGeoFiles["geofile_sndlhc_TI18_V0_"+str(year)+".root"] = commonPath+str(year)+"/" def modifyDicts(year=2024): + if year not in supported_years: + print("Geometry for the required year "+str(year)+"is not supported.\n\ + The list of supported years is:", supported_years) + return for geoFileName in supportedGeoFiles: - if (year == 2024 and geoFileName.find(str(year)) > 0) or \ - (year != 2024 and geoFileName.find('2022') > 0): + if geoFileName.find(str(year)) > 0: # override locally existing file with the same name status = theClient.copy(supportedGeoFiles[geoFileName]+geoFileName, os.getcwd()+'/'+geoFileName, force=True) From aeaa7b30f963db7480f07ca9252fab197b6bf8f0 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 20 Jun 2025 15:30:59 +0200 Subject: [PATCH 03/99] Add to the geofile only the calibration consts relevant for the specific year --- shipLHC/modifyGeoFileDict.py | 39 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index 33ed512f71..573f380b28 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -70,13 +70,14 @@ def modifyDicts(year=2024): constants['t_9692'] = [-6.58,-6.67,-6.76,-6.95,-6.47,-6.53,-6.07,-6.20,-6.22,-6.39,-6.01,-6.18,-8.23,-8.26,-8.43,-8.65,-8.19,-8.27,-8.20,-8.35] constants['t_9882'] = [-6.22,-6.13,-6.37,-6.25,-6.02,-6.18,-5.65,-5.58,-5.76,-5.79,-5.53,-5.75,-7.81,-7.69,-8.02,-8.07,-7.74,-7.85,-7.81,-7.98] constants['t_10012']= [-6.70,-6.84,-6.73,-6.86,-6.50,-6.64,-6.04,-6.19,-6.21,-6.41,-6.05,-6.24,-8.23,-8.38,-8.40,-8.64,-8.22,-8.38,-8.31,-8.52] - slopes_dict_2022 = {"t_0":0.000, "t_4361":0.082, "t_5117":0.085, "t_5478":0.082, "t_6208":0.086, "t_6443":0.082, "t_6677":0.084} - # new constants for 2024 and later on - slopes_dict_2024 = {"t_0":0.000, "t_7649":0.081, "t_8318":0.082, "t_8583":0.081, "t_8942":0.080, - "t_9156":0.083, "t_9286":0.082, "t_9379":0.083, "t_9462":0.083, "t_9613":0.082, - "t_9692":0.078, "t_9882":0.084, "t_10012":0.085} - if year == 2024: slopes_dict = slopes_dict_2024 - else: slopes_dict = slopes_dict_2022 + ds_time_aligment_consts = { + 2022: {"t_0":0.000, "t_4361":0.082, "t_5117":0.085}, + 2023: {"t_0":0.000, "t_5478":0.082, "t_6208":0.086, "t_6443":0.082, "t_6677":0.084}, + 2024: {"t_0":0.000, "t_7649":0.081, "t_8318":0.082, "t_8583":0.081, "t_8942":0.080, + "t_9156":0.083, "t_9286":0.082, "t_9379":0.083, "t_9462":0.083, "t_9613":0.082, + "t_9692":0.078, "t_9882":0.084, "t_10012":0.085} + } + slopes_dict = ds_time_aligment_consts[year] #time delay corrections first order, only for DS at the moment for p in slopes_dict.keys(): setattr(sGeo.MuFilter,'DSTcorslope'+p,slopes_dict[p]) @@ -184,11 +185,13 @@ def modifyDicts(year=2024): -0.406*u.ns, 0.000*u.ns, -1.319*u.ns, 0.337*u.ns, -2.012*u.ns, -1.059*u.ns, -0.618*u.ns, -0.825*u.ns, 0.000*u.ns, -1.205*u.ns, -0.952*u.ns, 0.241*u.ns, -1.363*u.ns, 1.123*u.ns ] # - scifi_time_tags_2022 = ['t_0', 't_4361','t_5117', 't_5478', 't_6208', 't_6443', 't_6677'] - scifi_time_tags_2024 = ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', - 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'] - if year == 2024: scifi_time_tags = scifi_time_tags_2024 - else: scifi_time_tags = scifi_time_tags_2022 + scifi_time_aligment_consts = { + 2022: ['t_0', 't_4361','t_5117'], + 2023: ['t_0', 't_5478', 't_6208', 't_6443', 't_6677'], + 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', + 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'] + } + scifi_time_tags = scifi_time_aligment_consts[year] for c in scifi_time_tags: k=0 for s in range(1,6): @@ -523,11 +526,13 @@ def modifyDicts(year=2024): 0*u.mrad, -0.79*u.mrad, 0*u.mrad, 0*u.mrad, 2.93*u.mrad, 0*u.mrad] - scifi_spatial_tags_2022 = ['t_0', 't_4361','t_4575','t_4855','t_5172','t_5431', 't_6443', 't_6677'] - scifi_spatial_tags_2024 = ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', - 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'] - if year == 2024: scifi_spatial_tags = scifi_spatial_tags_2024 - else: scifi_spatial_tags = scifi_spatial_tags_2022 + scifi_spatial_aligment_consts = { + 2022: ['t_0', 't_4361','t_4575','t_4855','t_5172'], + 2023: ['t_0', 't_5431', 't_6443', 't_6677'], + 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', + 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'] + } + scifi_spatial_tags = scifi_spatial_aligment_consts[year] for c in scifi_spatial_tags: k=0 for s in range(1,6): From 91760a750a058eb872d94a7ca2cdebfba7793d39 Mon Sep 17 00:00:00 2001 From: schuetha Date: Tue, 1 Jul 2025 20:49:56 +0700 Subject: [PATCH 04/99] Adapt sndlhcGSimpleNtpConverter to new more detailed FLUKA input Use input data to adjust the scoring plane (flux window in Genie)! Ran Clang-formatting on the file. --- shipgen/sndlhcGSimpleNtpConverter.cxx | 449 +++++++++++++++----------- 1 file changed, 256 insertions(+), 193 deletions(-) diff --git a/shipgen/sndlhcGSimpleNtpConverter.cxx b/shipgen/sndlhcGSimpleNtpConverter.cxx index 5faff044c0..827cb2b584 100644 --- a/shipgen/sndlhcGSimpleNtpConverter.cxx +++ b/shipgen/sndlhcGSimpleNtpConverter.cxx @@ -15,199 +15,262 @@ Converts neutrino rays generated by FLUKA into GSimpleNtpFlux format for GENIE */ -int main(int argc, char** argv){ - - if (argc != 4) { - std::cout << "Three arguments required: path to FLUKA file AND output file name AND number of pp collisions used to generate FLUKA file." << std::endl; - return -1; - } - - std::string inFileName = std::string(argv[1]); - std::string outFileName = std::string(argv[2]); - double pp_collision_number = std::stod(argv[3]); - - // Convert FLUKA to PDG particle IDs. (ONLY NEUTRINOS IMPLEMENTED!!!) - std::map FLUKAtoPDG { {27, 14}, - {28, -14}, - {5, 12}, - {6, -12}, - {43, 16}, - {44, -16} }; - - // To generate a random seed which is stored in the metadata. - TRandom3 * ran = new TRandom3(); - - bool verbose = false; - - // Scoring plane info - // Plane z in FLUKA coordinates: 48386 cm - // FLUKA z = 0 in sndsw coordinates : -48000 cm - double z = (48386-48000); // In cm for consistency with the FLUKA file. - - double plane_corner[] = {-70., 5., z}; - double plane_dir1[] = {140., 0, 0}; - double plane_dir2[] = {0, 65., 0}; - - // Set up input file - std::ifstream in_file(inFileName); - - // Input variables - /* - # Scoring particles entering Region No 2935 - # Col 1: FLUKA run number - # Col 2: primary event number - # -- Particle information -- - # Col 3: FLUKA particle type ID - # Col 4: Generation number - # Col 5: Kinetic energy (GeV) - # Col 6: Statistical weight - # -- Crossing at scoring plane -- - # Col 7: x coord (cm) - # Col 8: y coord (cm) - # Col 9: x dir cosine - # Col 10: y dir cosine - # Col 11: Particle age since primary event (sec) - # Col 12: ... +int main(int argc, char **argv) +{ + + if (argc != 4) { + std::cout << "Three arguments required: path to FLUKA file AND output file name AND number of pp collisions used " + "to generate FLUKA file." + << std::endl; + return -1; + } + + std::string inFileName = std::string(argv[1]); + std::string outFileName = std::string(argv[2]); + double pp_collision_number = std::stod(argv[3]); + + // Convert FLUKA to PDG particle IDs. (ONLY NEUTRINOS IMPLEMENTED!!!) + std::map FLUKAtoPDG{{27, 14}, {28, -14}, {5, 12}, {6, -12}, {43, 16}, {44, -16}}; + + // To generate a random seed which is stored in the metadata. + TRandom3 *ran = new TRandom3(); + + bool verbose = false; + + std::cout << "Start converting" << std::endl; + + // Set up input file + std::ifstream in_file(inFileName); + + // Input variables + /* + # Scoring particles entering Region No 2935 + # Col 1: FLUKA run number + # Col 2: primary event number + # -- Particle information -- + # Col 3: FLUKA particle type ID + # Col 4: Generation number + # Col 5: Kinetic energy (GeV) + # Col 6: Statistical weight + # -- Crossing at scoring plane -- + # Col 7: x coord (cm) + # Col 8: y coord (cm) + # Col 9: x dir cosine + # Col 10: y dir cosine + # Col 11: z coord (cm) + # Col 12: Particle age since primary event (sec) + # Col 13: Last decay x cooord (cm) + # Col 14: Last decay y cooord (cm) + # Col 15: Last decay z cooord (cm) + # Col 16: Last decay ID + # Col 17: Kinetic energy of decay parent (GeV) + # Col 18: Last interaction x cooord (cm) + # Col 19: Last interaction y cooord (cm) + # Col 20: Last interaction z cooord (cm) + # Col 21: Last interaction ID + # Col 22: Kinetic energy of parent (GeV) */ - int FlukaRun, evtNumber, FlukaID, generationN; - float Ekin, weight, x, y, x_cos, y_cos, age; - - TFile * fOut = new TFile(outFileName.c_str(), "RECREATE"); - TTree * tOut = new TTree("flux", "a simple flux n-tuple"); - TTree * metaOut = new TTree("meta","metadata for flux n-tuple"); - - genie::flux::GSimpleNtpEntry * gsimple_entry = new genie::flux::GSimpleNtpEntry; - tOut->Branch("entry", &gsimple_entry); - genie::flux::GSimpleNtpMeta* meta_entry = new genie::flux::GSimpleNtpMeta; - metaOut->Branch("meta", &meta_entry); - - genie::flux::GSimpleNtpAux* aux_entry = new genie::flux::GSimpleNtpAux; - tOut->Branch("aux", &aux_entry); - - - UInt_t metakey = TString::Hash(outFileName.c_str(),strlen(outFileName.c_str())); - metakey &= 0x7FFFFFFF; - - // Metadata accumulators: - std::set pdglist; - double min_weight = 1e10; - double max_weight = -1e10; - double max_energy = 0; - - unsigned long int counter = 0; - - if (in_file.is_open()){ - string in_line; - while (!in_file.eof()){ - getline(in_file, in_line); - - // Skip lines containing "#" - if (in_line.find("#") == in_line.npos){ - in_file >> FlukaRun - >> evtNumber - >> FlukaID - >> generationN - >> Ekin - >> weight - >> x - >> y - >> x_cos - >> y_cos - >> age; - - gsimple_entry->Reset(); - aux_entry->Reset(); - - - if (verbose) std::cout << "Got entry " << counter++ << std::endl; - - if (verbose){ - std::cout << FlukaRun << "\n" - << evtNumber << "\n" - << FlukaID << "\n" - << generationN << "\n" - << Ekin << "\n" - << weight << "\n" - << x << "\n" - << y << "\n" - << z << "\n" - << x_cos << "\n" - << y_cos << "\n" - << age << "\n" - << "--------------------------------------------------" << std::endl; - } - - gsimple_entry->metakey = metakey; - gsimple_entry->pdg = FLUKAtoPDG[FlukaID]; - gsimple_entry->wgt = weight; - gsimple_entry->vtxx = x*ShipUnit::cm/ShipUnit::m; // in m - gsimple_entry->vtxy = y*ShipUnit::cm/ShipUnit::m; - gsimple_entry->vtxz = z*ShipUnit::cm/ShipUnit::m; - gsimple_entry->dist = 0.; // Distance from hadron decay point to neutrino "vertex", to use for oscillations, for example. Don't use. - - // I'm assuming x_cos, y_cos are normalized. - double z_cos = sqrt(1 - pow(x_cos, 2) - pow(y_cos, 2)); - // And massless neutrinos - gsimple_entry->px = Ekin*x_cos; // in GeV/c - gsimple_entry->py = Ekin*y_cos; - gsimple_entry->pz = Ekin*z_cos; - gsimple_entry->E = Ekin; - - // Set auxiliary data - aux_entry->auxint.push_back(FlukaRun); - aux_entry->auxint.push_back(evtNumber); - aux_entry->auxint.push_back(FlukaID); - aux_entry->auxint.push_back(generationN); - - aux_entry->auxdbl.push_back(age); - aux_entry->auxdbl.push_back(weight); - - // Accumulate metadata - pdglist.insert(gsimple_entry->pdg); - min_weight = TMath::Min(min_weight, gsimple_entry->wgt); - max_weight = TMath::Max(max_weight, gsimple_entry->wgt); - max_energy = TMath::Max(max_energy, gsimple_entry->E); - - // All done! - tOut->Fill(); + int FlukaRun, evtNumber, FlukaID, generationN, last_decay_ID, last_interaction_ID; + double Ekin, weight, x, y, z, x_cos, y_cos, age, Ekin_parent; + double last_decay_x, last_decay_y, last_decay_z, Ekin_decay; + double last_interaction_x, last_interaction_y, last_interaction_z; + + // Scoring plane info + // Find a Minimum and Maximum of the x, y, z axis (FLUKA coordinates) cm + // Max z, min z + double min_z = 999; + double max_z = -999; + // Max x, min x + double min_x = 999; + double max_x = -999; + // Max y, min y + double min_y = 999; + double max_y = -999; + + TFile *fOut = new TFile(outFileName.c_str(), "RECREATE"); + TTree *tOut = new TTree("flux", "a simple flux n-tuple"); + TTree *metaOut = new TTree("meta", "metadata for flux n-tuple"); + + genie::flux::GSimpleNtpEntry *gsimple_entry = new genie::flux::GSimpleNtpEntry; + tOut->Branch("entry", &gsimple_entry); + genie::flux::GSimpleNtpMeta *meta_entry = new genie::flux::GSimpleNtpMeta; + metaOut->Branch("meta", &meta_entry); + + genie::flux::GSimpleNtpAux *aux_entry = new genie::flux::GSimpleNtpAux; + tOut->Branch("aux", &aux_entry); + + UInt_t metakey = TString::Hash(outFileName.c_str(), strlen(outFileName.c_str())); + metakey &= 0x7FFFFFFF; + + // Metadata accumulators: + std::set pdglist; + double min_weight = 1e10; + double max_weight = -1e10; + double max_energy = 0; + + unsigned long int counter = 0; + + if (in_file.is_open()) { + string in_line; + + while (!in_file.eof()) { + getline(in_file, in_line); + + // Skip lines containing "#" + if (in_line.find("#") == in_line.npos) { + in_file >> FlukaRun >> evtNumber >> FlukaID >> generationN >> Ekin >> weight >> x >> y >> x_cos >> y_cos >> + z >> age >> last_decay_x >> last_decay_y >> last_decay_z >> last_decay_ID >> Ekin_decay >> + last_interaction_x >> last_interaction_y >> last_interaction_z >> last_interaction_ID >> Ekin_parent; + + gsimple_entry->Reset(); + aux_entry->Reset(); + + // FLUKA z = 0 in sndsw coordinates : -48000 cm + z -= 48000; // In cm for consistency with the FLUKA file. + + min_z = std::min(min_z, z); + max_z = std::max(max_z, z); + min_x = std::min(min_x, x); + max_x = std::max(max_x, x); + min_y = std::min(min_y, y); + max_y = std::max(max_y, y); + + if (verbose) + std::cout << "Got entry " << counter++ << std::endl; + + if (verbose) { + std::cout << FlukaRun << "\n" + << evtNumber << "\n" + << FlukaID << "\n" + << generationN << "\n" + << Ekin << "\n" + << weight << "\n" + << x << "\n" + << y << "\n" + << x_cos << "\n" + << y_cos << "\n" + << z << "\n" + << age << "\n" + << last_decay_x << "\n" + << last_decay_y << "\n" + << last_decay_z << "\n" + << last_decay_ID << "\n" + << Ekin_decay << "\n" + << last_interaction_x << "\n" + << last_interaction_y << "\n" + << last_interaction_z << "\n" + << last_interaction_ID << "\n" + << Ekin_parent << "\n" + << "--------------------------------------------------" << std::endl; + } + + gsimple_entry->metakey = metakey; + gsimple_entry->pdg = FLUKAtoPDG[FlukaID]; + gsimple_entry->wgt = weight; + gsimple_entry->vtxx = x * ShipUnit::cm / ShipUnit::m; // in m + gsimple_entry->vtxy = y * ShipUnit::cm / ShipUnit::m; + gsimple_entry->vtxz = z * ShipUnit::cm / ShipUnit::m; + gsimple_entry->dist = 0.; // Distance from hadron decay point to neutrino "vertex", to use for oscillations, + // for example. Don't use. + + // I'm assuming x_cos, y_cos are normalized. + double z_cos = sqrt(1 - pow(x_cos, 2) - pow(y_cos, 2)); + // And massless neutrinos + gsimple_entry->px = Ekin * x_cos; // in GeV/c + gsimple_entry->py = Ekin * y_cos; + gsimple_entry->pz = Ekin * z_cos; + gsimple_entry->E = Ekin; + + // Set auxiliary data + aux_entry->auxint.push_back(FlukaRun); + aux_entry->auxint.push_back(evtNumber); + aux_entry->auxint.push_back(FlukaID); + aux_entry->auxint.push_back(generationN); + aux_entry->auxint.push_back(last_decay_ID); + aux_entry->auxint.push_back(last_interaction_ID); + aux_entry->auxdbl.push_back(age); + aux_entry->auxdbl.push_back(weight); + aux_entry->auxdbl.push_back(Ekin); + aux_entry->auxdbl.push_back(x); + aux_entry->auxdbl.push_back(y); + aux_entry->auxdbl.push_back(z); + aux_entry->auxdbl.push_back(x_cos); + aux_entry->auxdbl.push_back(y_cos); + aux_entry->auxdbl.push_back(last_decay_x); + aux_entry->auxdbl.push_back(last_decay_y); + aux_entry->auxdbl.push_back(last_decay_z); + aux_entry->auxdbl.push_back(Ekin_decay); + aux_entry->auxdbl.push_back(last_interaction_x); + aux_entry->auxdbl.push_back(last_interaction_y); + aux_entry->auxdbl.push_back(last_interaction_z); + aux_entry->auxdbl.push_back(Ekin_parent); + + // Accumulate metadata + pdglist.insert(gsimple_entry->pdg); + min_weight = TMath::Min(min_weight, gsimple_entry->wgt); + max_weight = TMath::Max(max_weight, gsimple_entry->wgt); + max_energy = TMath::Max(max_energy, gsimple_entry->E); + + // All done! + tOut->Fill(); + } } - } - } - - // Sort out metadata - // Copy pdg list - meta_entry->pdglist.clear(); - for (std::set::const_iterator pdg_iterator = pdglist.begin(); - pdg_iterator != pdglist.end(); - ++pdg_iterator) meta_entry->pdglist.push_back(*pdg_iterator); - meta_entry->maxEnergy = max_energy; - meta_entry->maxWgt = max_weight; - meta_entry->minWgt = min_weight; - - meta_entry->protons = pp_collision_number; // Number of pp collisions. - for (int i = 0; i < 3; i++) meta_entry->windowBase[i] = plane_corner[i]*ShipUnit::cm/ShipUnit::m; - for (int i = 0; i < 3; i++) meta_entry->windowDir1[i] = plane_dir1[i]*ShipUnit::cm/ShipUnit::m; - for (int i = 0; i < 3; i++) meta_entry->windowDir2[i] = plane_dir2[i]*ShipUnit::cm/ShipUnit::m; - - meta_entry->infiles.push_back(inFileName); - meta_entry->seed = ran->GetSeed(); - meta_entry->metakey = metakey; - - meta_entry->auxintname.push_back("FlukaRun"); - meta_entry->auxintname.push_back("evtNumber"); - meta_entry->auxintname.push_back("FlukaID"); - meta_entry->auxintname.push_back("generationN"); - - meta_entry->auxdblname.push_back("age"); - meta_entry->auxdblname.push_back("FLUKA_weight"); - - metaOut->Fill(); - - fOut->cd(); - metaOut->Write(); - tOut->Write(); - fOut->Close(); - -} - + } + + // plane corner and plane direction of the scoring plane (The scoring plane is tilted) + double plane_corner[] = {min_x, min_y, min_z}; + double plane_dir1[] = {max_x - min_x, 0, max_z - min_z}; + double plane_dir2[] = {0, max_y - min_y, max_z - min_z}; + + // Sort out metadata + // Copy pdg list + meta_entry->pdglist.clear(); + for (std::set::const_iterator pdg_iterator = pdglist.begin(); pdg_iterator != pdglist.end(); ++pdg_iterator) + meta_entry->pdglist.push_back(*pdg_iterator); + meta_entry->maxEnergy = max_energy; + meta_entry->maxWgt = max_weight; + meta_entry->minWgt = min_weight; + + meta_entry->protons = pp_collision_number; // Number of pp collisions. + for (int i = 0; i < 3; i++) + meta_entry->windowBase[i] = plane_corner[i] * ShipUnit::cm / ShipUnit::m; + for (int i = 0; i < 3; i++) + meta_entry->windowDir1[i] = plane_dir1[i] * ShipUnit::cm / ShipUnit::m; + for (int i = 0; i < 3; i++) + meta_entry->windowDir2[i] = plane_dir2[i] * ShipUnit::cm / ShipUnit::m; + + meta_entry->infiles.push_back(inFileName); + meta_entry->seed = ran->GetSeed(); + meta_entry->metakey = metakey; + + meta_entry->auxintname.push_back("FlukaRun"); + meta_entry->auxintname.push_back("evtNumber"); + meta_entry->auxintname.push_back("FlukaID"); + meta_entry->auxintname.push_back("generationN"); + meta_entry->auxintname.push_back("Last decay ID"); + meta_entry->auxintname.push_back("Last interaction ID"); + meta_entry->auxdblname.push_back("age"); + meta_entry->auxdblname.push_back("FLUKA_weight"); + meta_entry->auxdblname.push_back("Kenetic energy"); + meta_entry->auxdblname.push_back("X coordinate"); + meta_entry->auxdblname.push_back("Y coordinate"); + meta_entry->auxdblname.push_back("X cosine"); + meta_entry->auxdblname.push_back("Y cosine"); + meta_entry->auxdblname.push_back("Last decay X coordinate"); + meta_entry->auxdblname.push_back("Last decay Y coordinate"); + meta_entry->auxdblname.push_back("Last decay Z coordinate"); + meta_entry->auxdblname.push_back("Kenetic Energy of decay parent"); + meta_entry->auxdblname.push_back("Last interaction in Z coordinate"); + meta_entry->auxdblname.push_back("Last in interaction Y coordinate"); + meta_entry->auxdblname.push_back("Last in interaction Z coordinate"); + meta_entry->auxdblname.push_back("Kenetic energy of parent"); + + metaOut->Fill(); + + fOut->cd(); + metaOut->Write(); + tOut->Write(); + fOut->Close(); + std::cout << "Done converting" << std::endl; +} \ No newline at end of file From ffcea18bc2ea284d4007001398e37ab84f1fba1b Mon Sep 17 00:00:00 2001 From: siilieva <84918585+siilieva@users.noreply.github.com> Date: Mon, 30 Jun 2025 12:32:47 +0200 Subject: [PATCH 05/99] Add CHANGELOG to keep track of sw changes and releases Includes a brief description of the software versioning convention to be followed. We start off with the first release and list of additions/changes/fixes. --- CHANGELOG.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..53a591d9ca --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog + +All notable changes to this project will be documented in this file. +The latest changes are placed on top get assigned to the corresponding +software release. + +The software versioning scheme is vMAJOR.MINOR.PATCH+DATE-comment, where + - PATCH - quick bug fixes + - MINOR - new classes or functionality, updates to adapt to new external packages + - MAJOR - additions of the detectors, breaking changes - hopefully we won't have such + - DATE - to ease communication and mimic our CVMFS releases + - comment - a short description of changes if available + - e.g. v1.0.0+2025-07-updateScifi + + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +There could be several types of changes: +- `Added` for new features. +- `Changed` for changes in existing functionality. +- `Deprecated` for soon-to-be removed features. +- `Removed` for now removed features. +- `Fixed` for any bug fixes. +- `Security` in case of vulnerabilities. + +We start with the first sndsw release: v1.0.0+2025-07-updateScifi. +Shall there be a strong will/need, one can go back and create and +fill in the logs for previous stacks. + +## v1.0.0+2025-07-updateScifi + +### Added + +- CHANGELOG +- Data file and geometry getters from run number + +### Changed + +- Major update on the TI18 and baby SciFi detector geometry +- Update on the testbeams system geometry to take care of air gaps + +### Fixed + +- Units for energy and snd_TDC2ns in the cpp units header +- storage of Emulsion(using a flag) and Veto points(always) +- Adapted to the latest FairRoot v19 + +### Removed + +- TI18 MC geometry config file is gone. We use a single one for data and simulation From 77007bda39098a9de6e4944b2b549adea81265c9 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 4 Jul 2025 17:06:21 +0200 Subject: [PATCH 06/99] Fix flag logic to allow choosing the 2024 testbeam W target --- shipLHC/EmulsionDet.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shipLHC/EmulsionDet.cxx b/shipLHC/EmulsionDet.cxx index 33a5734ceb..ec7ecaed0f 100644 --- a/shipLHC/EmulsionDet.cxx +++ b/shipLHC/EmulsionDet.cxx @@ -195,6 +195,9 @@ void EmulsionDet::ConstructGeometry() int NTungstenPlatesTB24 = conf_ints["EmulsionDet/n_tungsten_plates_tb24"]; bool testbeam_2024_setup = false; + if (NTungstenPlatesTB24 > 0) { + testbeam_2024_setup = true; + } TGeoVolume *top=gGeoManager->FindVolumeFast("Detector"); if(!top) LOG(ERROR) << "no Detector volume found " ; From 5269db9f6aa9fbbb48ab73534d066750353736ef Mon Sep 17 00:00:00 2001 From: siilieva Date: Wed, 9 Jul 2025 09:11:01 +0200 Subject: [PATCH 07/99] Unit fix for the selectScifiHits default The here-fixed time_lower_range unit was correctly set everywhere else in the SciFi tools --- analysis/tools/sndSciFiTools.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analysis/tools/sndSciFiTools.h b/analysis/tools/sndSciFiTools.h index f8967dfe33..9281ee0ebe 100644 --- a/analysis/tools/sndSciFiTools.h +++ b/analysis/tools/sndSciFiTools.h @@ -39,7 +39,7 @@ namespace snd { // selection_parameters[3] // make_selection determines whether you want to select the hits for the respective plane // or not and skip that step (in case the hits are already curated) - std::unique_ptr selectScifiHits(const TClonesArray &digiHits, int station, bool orientation, int bins_x=52, float min_x=0.0, float max_x=26.0, float time_lower_range=1E9/(2*ShipUnit::snd_freq), float time_upper_range=1.2E9/(ShipUnit::snd_freq/ShipUnit::hertz), bool make_selection=true, bool isMC=false); + std::unique_ptr selectScifiHits(const TClonesArray &digiHits, int station, bool orientation, int bins_x=52, float min_x=0.0, float max_x=26.0, float time_lower_range=1E9/(2*ShipUnit::snd_freq/ShipUnit::hertz), float time_upper_range=1.2E9/(ShipUnit::snd_freq/ShipUnit::hertz), bool make_selection=true, bool isMC=false); std::unique_ptr selectScifiHits(const TClonesArray &digiHits, int station, bool orientation, const std::map &selection_parameters, bool make_selection=true, bool isMC=false); // Function to obtain the ScifiHits that are considered to be useful from an event From 5ab8a06902c12045a1d39b1c3ba1d96b812aa69d Mon Sep 17 00:00:00 2001 From: siilieva <84918585+siilieva@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:28:40 +0200 Subject: [PATCH 08/99] 2dED: Add option to filter SciFi hits by time * 2dED: Add option to filter SciFi hits by time It is basically using one of the existing SciFi analysis tools. Added informative control and helper messages to quide the user how to use the option FilterScifiHits with the loopEvents() function. --- shipLHC/scripts/2dEventDisplay.py | 54 +++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/shipLHC/scripts/2dEventDisplay.py b/shipLHC/scripts/2dEventDisplay.py index 204804064f..4ca03e0a60 100644 --- a/shipLHC/scripts/2dEventDisplay.py +++ b/shipLHC/scripts/2dEventDisplay.py @@ -14,7 +14,12 @@ from datetime import datetime from pathlib import Path +import logging +logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.WARNING) +logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.FATAL) + ROOT.gStyle.SetPalette(ROOT.kViridis) +ROOT.gInterpreter.ProcessLine('#include "'+os.environ['SNDSW_ROOT']+'/analysis/tools/sndSciFiTools.h"') def pyExit(): "unfortunately need as bypassing an issue related to use xrootd" @@ -296,11 +301,32 @@ def loopEvents( minSipmMult=1, withTiming=False, option=None, - Setup='', + Setup='TI18', verbose=0, auto=False, - hitColour=None + hitColour=None, + FilterScifiHits=None ): + + # check the format of FilterScifiHits if set + if FilterScifiHits: + important_keys = {"bins_x", "min_x", "max_x", "time_lower_range", "time_upper_range"} + all_keys = important_keys.copy() + all_keys.add("method") + filter_parameters = {"bins_x":52., "min_x":0., "max_x":26., + "time_lower_range":1E9/(2*u.snd_freq/u.hertz), + "time_upper_range":2E9/(u.snd_freq/u.hertz), + "method":0} + if FilterScifiHits!="default" and not important_keys.issubset(FilterScifiHits): + logging.fatal("Invalid FilterScifiHits format. Two options are supported:\n" + "#1 FilterScifiHits = 'default'\nwhich sets the default parameters:\n"+ + str(filter_parameters)+" or\n" + "#2 FilterScifiHits = filter_dictionary \nwhere filter_dictionary has all of the following keys\n"+ + str(important_keys)+"\nAn additional key 'method' exists: its single supported value, also default, is 0.") + return + if FilterScifiHits!="default" and any(k not in all_keys for k in FilterScifiHits): + logging.warning("Ignoring provided keys other than "+str(all_keys)) + if 'simpleDisplay' not in h: ut.bookCanvas(h,key='simpleDisplay',title='simple event display',nx=1200,ny=1600,cx=1,cy=2) h['simpleDisplay'].cd(1) # TI18 coordinate system @@ -403,7 +429,29 @@ def loopEvents( if nAlltracks > 0: print('total number of tracks: ', nAlltracks) digis = [] - if event.FindBranch("Digi_ScifiHits"): digis.append(event.Digi_ScifiHits) + if event.FindBranch("Digi_ScifiHits"): + method = 0 + if FilterScifiHits!=None and FilterScifiHits!="default": + filter_parameters = {k: FilterScifiHits[k] for k in important_keys if k in FilterScifiHits} + method = FilterScifiHits.get("method", 0) # set to the default 0, if item is not provided + if FilterScifiHits and (Setup=="TI18" or Setup=="H8" or Setup=="H4"): + setup = Setup + # Only H8 is explicitly supported in the SciFi tools. However, the same baby SciFi + # system was reused in H4. It is then safe to use the SciFi tools for H4 as well. + if Setup =="H4": + setup ="H8" + # Convert the filter_parameters to the needed std.map format + selection_parameters = ROOT.std.map('string', 'float')() + selection_parameters["bins_x"] = float(filter_parameters["bins_x"]) + selection_parameters["min_x"] = float(filter_parameters["min_x"]) + selection_parameters["max_x"] = float(filter_parameters["max_x"]) + selection_parameters["time_lower_range"] = float(filter_parameters["time_lower_range"]) + selection_parameters["time_upper_range"] = float(filter_parameters["time_upper_range"]) + digis.append(ROOT.snd.analysis_tools.filterScifiHits(event.Digi_ScifiHits,selection_parameters,method,setup,mc)) + else: + if FilterScifiHits: + logging.warning(Setup+" is not supported for the time-filtering of SciFi hits, using all hits instead.") + digis.append(event.Digi_ScifiHits) if event.FindBranch("Digi_MuFilterHits"): digis.append(event.Digi_MuFilterHits) if event.FindBranch("Digi_MuFilterHit"): digis.append(event.Digi_MuFilterHit) empty = True From 81340c94abe8430cd94dffd394c03e39d1cd03c9 Mon Sep 17 00:00:00 2001 From: cvilelahep Date: Wed, 9 Jul 2025 16:40:43 +0200 Subject: [PATCH 09/99] Adding various options to 2dEventDisplay. (#245) --no-extra-info to suppresses printing the detailed event time/QDC information --extension allows for choosing the format of the saved file. E.g., png, pdf , root, etc. --rootbatch runs in ROOT batch mode --collision-axis draws the collision axis as a dotted red line in (x, y) = (0, 0) --------- Co-authored-by: Cristovao Vilela Co-authored-by: Oliver Lantwin --- shipLHC/scripts/2dEventDisplay.py | 52 ++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/shipLHC/scripts/2dEventDisplay.py b/shipLHC/scripts/2dEventDisplay.py index 4ca03e0a60..9e9571fb56 100644 --- a/shipLHC/scripts/2dEventDisplay.py +++ b/shipLHC/scripts/2dEventDisplay.py @@ -40,12 +40,24 @@ def pyExit(): parser.add_argument("-g", "--geoFile", dest="geoFile", help="geofile", default=os.environ["EOSSHIP"]+"/eos/experiment/sndlhc/convertedData/physics/2022/geofile_sndlhc_TI18_V0_2022.root") parser.add_argument("-P", "--partition", dest="partition", help="partition of data", type=int,required=False,default=-1) parser.add_argument("--server", dest="server", help="xrootd server",default=os.environ["EOSSHIP"]) -parser.add_argument("-X", dest="extraInfo", help="print extra event info",default=True) +parser.add_argument("--no-extraInfo", dest="extraInfo", action="store_false", help="Do not print extra information on the event.") + +parser.add_argument("--extension", help="Extension of file to save. E.g. png, pdf, root, etc.", default="png") +parser.add_argument("--rootbatch", help="Run ROOT in batch mode.", action="store_true") + +parser.add_argument("--collision_axis", dest="drawCollAxis", help="Draw collision axis", action="store_true") parser.add_argument("-par", "--parFile", dest="parFile", help="parameter file", default=os.environ['SNDSW_ROOT']+"/python/TrackingParams.xml") parser.add_argument("-hf", "--HoughSpaceFormat", dest="HspaceFormat", help="Hough space representation. Should match the 'Hough_space_format' name in parFile, use quotes", default='linearSlopeIntercept') options = parser.parse_args() + +resolution_factor = 1 +if options.rootbatch: + ROOT.gROOT.SetBatch() + # Produce figures larger than the screen resolution. E.g., for printing. + resolution_factor = 2 + options.storePic = '' trans2local = False runInfo = False @@ -327,7 +339,9 @@ def loopEvents( if FilterScifiHits!="default" and any(k not in all_keys for k in FilterScifiHits): logging.warning("Ignoring provided keys other than "+str(all_keys)) - if 'simpleDisplay' not in h: ut.bookCanvas(h,key='simpleDisplay',title='simple event display',nx=1200,ny=1600,cx=1,cy=2) + if 'simpleDisplay' not in h: + ut.bookCanvas(h,key='simpleDisplay',title='simple event display',nx=1200,ny=1600,cx=1,cy=2) + h['simpleDisplay'].cd(1) # TI18 coordinate system zStart = 250. @@ -488,6 +502,10 @@ def loopEvents( rc = h[ 'simpleDisplay'].cd(p) h[proj[p]].Draw('b') + if options.drawCollAxis: + for k in proj: + drawCollisionAxis(h['simpleDisplay'], k) + if withDetector: drawDetectors() for D in digis: @@ -624,18 +642,20 @@ def loopEvents( if verbose>0: dumpChannels() userProcessing(event) - if save: h['simpleDisplay'].Print('{:0>2d}-event_{:04d}'.format(runId,N)+'.png') + if save: + h['simpleDisplay'].Print('{:0>2d}-event_{:04d}'.format(runId, N) + '.' + options.extension) if auto: - h['simpleDisplay'].Print(options.storePic+str(runId)+'-event_'+str(event.EventHeader.GetEventNumber())+'.png') + h['simpleDisplay'].Print(options.storePic + str(runId) + '-event_' + str(event.EventHeader.GetEventNumber()) + '.' + options.extension) if not auto: rc = input("hit return for next event or p for print or q for quit: ") if rc=='p': - h['simpleDisplay'].Print(options.storePic+str(runId)+'-event_'+str(event.EventHeader.GetEventNumber())+'.png') + h['simpleDisplay'].Print(options.storePic + str(runId) + '-event_' + str(event.EventHeader.GetEventNumber()) + '.' + options.extension) elif rc == 'q': break else: eventComment[f"{runId}-event_{event.EventHeader.GetEventNumber()}"] = rc - if save: os.system("convert -delay 60 -loop 0 event*.png animated.gif") + if save: + os.system("convert -delay 60 -loop 0 event*." + options.extension + " animated.gif") def addTrack(OT,scifi=False): xax = h['xz'].GetXaxis() @@ -1142,7 +1162,6 @@ def drawInfo(pad, k, run, event, timestamp,moreEventInfo=[]): padLogo.Draw() logo = ROOT.TImage.Open('$SNDSW_ROOT/shipLHC/Large__SND_Logo_black_cut.png') logo.SetConstRatio(True) - logo.DrawText(0, 0, 'SND', 98) padLogo.cd() logo.Draw() pad.cd(k) @@ -1185,3 +1204,22 @@ def drawInfo(pad, k, run, event, timestamp,moreEventInfo=[]): textInfo.DrawLatex(0.4, 0.9-dely*i, moreEventInfo[i]) pad.cd(k) +def drawCollisionAxis(pad, k): + line_name = "collision_axis_line_" + str(k) + h[line_name] = ROOT.TLine(h["zmin"], 0, h["zmax"], 0) + h[line_name].SetLineColor(ROOT.kRed) + h[line_name].SetLineStyle(2) + + text_name = "collision_axis_text_" + str(k) + h[text_name] = ROOT.TText(h["zmin"] + 8, 0 + 2, "Collision axis") + h[text_name].SetTextAlign(12) + h[text_name].SetTextFont(43) + h[text_name].SetTextSize(13 * resolution_factor) + h[text_name].SetTextColor(ROOT.kRed) + + pad.cd(k) + h[line_name].Draw() + h[text_name].Draw() + + + From 82e87a8703aa8e84a7d5661fceb45ef29542f0cf Mon Sep 17 00:00:00 2001 From: siilieva <84918585+siilieva@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:37:04 +0200 Subject: [PATCH 10/99] update changelog before patch release v1.0.1 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53a591d9ca..1b0beb64e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,17 @@ We start with the first sndsw release: v1.0.0+2025-07-updateScifi. Shall there be a strong will/need, one can go back and create and fill in the logs for previous stacks. +## v1.0.1+2025-07-fixTB24Wtarget + +### Fixed + +- flag logic to select target geometry for testbeam 2024 +- unit fix for selectScifiHits's `time_lower_range` default + +### Added + +- options for the 2dEvent display: filter hits in time, plot collision axis and more. + ## v1.0.0+2025-07-updateScifi ### Added From 9fd2bd29ed3bb2a9cbb72ce13959fe4856dac721 Mon Sep 17 00:00:00 2001 From: Filippo Mei <67705874+FelixofRivia@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:40:06 +0200 Subject: [PATCH 11/99] Introducing analysis classes for SciFi and US planes (#266) includes also configuration settings for analyses of TI18(2022-2023 and 2024-), and 2023 and 2024 testbeams --- analysis/tools/AnalysisToolsLinkDef.h | 7 +- analysis/tools/CMakeLists.txt | 4 + analysis/tools/sndConfiguration.cxx | 167 ++++++++++++++++ analysis/tools/sndConfiguration.h | 78 ++++++++ analysis/tools/sndPlaneTools.cxx | 65 ++++++ analysis/tools/sndPlaneTools.h | 21 ++ analysis/tools/sndScifiPlane.cxx | 278 ++++++++++++++++++++++++++ analysis/tools/sndScifiPlane.h | 64 ++++++ analysis/tools/sndUSPlane.cxx | 178 +++++++++++++++++ analysis/tools/sndUSPlane.h | 76 +++++++ 10 files changed, 937 insertions(+), 1 deletion(-) create mode 100644 analysis/tools/sndConfiguration.cxx create mode 100644 analysis/tools/sndConfiguration.h create mode 100644 analysis/tools/sndPlaneTools.cxx create mode 100644 analysis/tools/sndPlaneTools.h create mode 100644 analysis/tools/sndScifiPlane.cxx create mode 100644 analysis/tools/sndScifiPlane.h create mode 100644 analysis/tools/sndUSPlane.cxx create mode 100644 analysis/tools/sndUSPlane.h diff --git a/analysis/tools/AnalysisToolsLinkDef.h b/analysis/tools/AnalysisToolsLinkDef.h index f12356de71..bea0805feb 100644 --- a/analysis/tools/AnalysisToolsLinkDef.h +++ b/analysis/tools/AnalysisToolsLinkDef.h @@ -7,6 +7,10 @@ #pragma link C++ nestedclasses; #pragma link C++ nestedtypedef; +#pragma link C++ class snd::sndConfiguration+; +#pragma link C++ class snd::analysis_tools::sndScifiPlane+; +#pragma link C++ class snd::analysis_tools::sndUSPlane+; + #pragma link C++ namespace snd::analysis_tools; #pragma link C++ defined_in namespace snd::analysis_tools; @@ -33,6 +37,7 @@ #pragma link C++ function snd::analysis_tools::GetTChain(std::string); #pragma link C++ function snd::analysis_tools::GetGeoPath(const std::string &,int); #pragma link C++ function snd::analysis_tools::GetGeometry(std::string); -#pragma link C++ function snd::analysis_tools::GetGeometry(const std::string &,int); +#pragma link C++ function snd::analysis_tools::FillScifi(const snd::Configuration &, TClonesArray *, Scifi *); +#pragma link C++ function snd::analysis_tools::FillUS(const snd::Configuration &, TClonesArray *, MuFilter *); #endif diff --git a/analysis/tools/CMakeLists.txt b/analysis/tools/CMakeLists.txt index aed9e2e5e8..4b76e02a20 100644 --- a/analysis/tools/CMakeLists.txt +++ b/analysis/tools/CMakeLists.txt @@ -13,6 +13,10 @@ set(SRCS sndSciFiTools.cxx sndTchainGetter.cxx sndGeometryGetter.cxx +sndConfiguration.cxx +sndScifiPlane.cxx +sndUSPlane.cxx +sndPlaneTools.cxx ) set(LINK_DIRECTORIES diff --git a/analysis/tools/sndConfiguration.cxx b/analysis/tools/sndConfiguration.cxx new file mode 100644 index 0000000000..bdd40727b8 --- /dev/null +++ b/analysis/tools/sndConfiguration.cxx @@ -0,0 +1,167 @@ +#include "sndConfiguration.h" + +#include +#include +#include + +#include "Scifi.h" +#include "MuFilter.h" + +snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter *muon_filter_geometry) +{ + // Parameters from geometry + scifi_n_stations = scifi_geometry->GetConfParI("Scifi/nscifi"); + scifi_boards_per_plane = scifi_geometry->GetConfParI("Scifi/nmats"); + scifi_n_channels_per_plane = scifi_geometry->GetConfParI("Scifi/nsipm_channels") * scifi_geometry->GetConfParI("Scifi/nmats") * scifi_geometry->GetConfParI("Scifi/nsipm_mat"); + scifi_fiber_lenght = scifi_geometry->GetConfParF("Scifi/fiber_length"); + scifi_centroid_error_x = scifi_geometry->GetConfParF("Scifi/channel_width"); + scifi_centroid_error_y = scifi_geometry->GetConfParF("Scifi/channel_width"); + scifi_centroid_error_z = scifi_geometry->GetConfParF("Scifi/channel_height"); + + veto_n_stations = muon_filter_geometry->GetConfParI("MuFilter/NVetoPlanes"); + + us_n_stations = muon_filter_geometry->GetConfParI("MuFilter/NUpstreamPlanes"); + us_bar_per_station = muon_filter_geometry->GetConfParI("MuFilter/NUpstreamBars"); + us_n_sipm_per_bar = muon_filter_geometry->GetConfParI("MuFilter/UpstreamnSiPMs") * muon_filter_geometry->GetConfParI("MuFilter/UpstreamnSides"); + us_n_channels_per_station = us_bar_per_station * us_n_sipm_per_bar; + us_centroid_error_x = muon_filter_geometry->GetConfParF("MuFilter/UpstreamBarX") / std::sqrt(12); + us_centroid_error_y = muon_filter_geometry->GetConfParF("MuFilter/UpstreamBarY") / std::sqrt(12); + us_centroid_error_z = muon_filter_geometry->GetConfParF("MuFilter/UpstreamBarZ"); + + ds_n_stations = muon_filter_geometry->GetConfParI("MuFilter/NDownstreamPlanes"); + ds_hor_spatial_resolution_x = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarX") / std::sqrt(12); + ds_hor_spatial_resolution_y = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarY") / std::sqrt(12); + ds_hor_spatial_resolution_z = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarZ") / std::sqrt(12); + ds_ver_spatial_resolution_x = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarX_ver") / std::sqrt(12); + ds_ver_spatial_resolution_y = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarY_ver") / std::sqrt(12); + ds_ver_spatial_resolution_z = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarZ_ver") / std::sqrt(12); + + // Common parameters not present in geometry + scifi_min_timestamp = -0.5; + scifi_max_timestamp = 0.5; + us_min_timestamp = -0.5; + us_max_timestamp = 3.0; + us_min_n_hits_for_centroid = 15; + us_qdc_to_gev = 0.0151; + + // Ad hoc parameters not present in geometry + if (option == Option::ti18_2024_2025) + { + scifi_shower_window_width = 128; + scifi_min_hits_in_window = 10; + scifi_min_n_hits_for_centroid = 5; + scifi_qdc_to_gev = 0.14; + + scifi_x_min = -50.0; + scifi_x_max = 0.0; + scifi_y_min = 10.0; + scifi_y_max = 60.0; + scifi_z_min = 280.0; + scifi_z_max = 360.0; + + us_x_min = -80.0; + us_x_max = 0.0; + us_y_min = 0.0; + us_y_max = 80.0; + us_z_min = 370.0; + us_z_max = 480.0; + + centroid_min_valid_station = 2; + } + + else if (option == Option::ti18_2022_2023) + { + scifi_shower_window_width = 128; + scifi_min_hits_in_window = 10; + scifi_min_n_hits_for_centroid = 5; + scifi_qdc_to_gev = 0.14; + scifi_x_min = -50.0; + scifi_x_max = 0.0; + scifi_y_min = 10.0; + scifi_y_max = 60.0; + scifi_z_min = 280.0; + scifi_z_max = 360.0; + scifi_min_timestamp = -0.5; + scifi_max_timestamp = 0.5; + + us_x_min = -80.0; + us_x_max = 0.0; + us_y_min = 0.0; + us_y_max = 80.0; + us_z_min = 370.0; + us_z_max = 480.0; + + centroid_min_valid_station = 2; + } + + else if (option == Option::test_beam_2023) + { + scifi_shower_window_width = 128; + scifi_min_hits_in_window = 36; + scifi_min_n_hits_for_centroid = 0; + scifi_qdc_to_gev = 0.053; // mirrors + + scifi_x_min = -47.0; + scifi_x_max = -27.0; + scifi_y_min = 35.0; + scifi_y_max = 55.0; + scifi_z_min = 310.0; + scifi_z_max = 360.0; + + us_x_min = -80.0; + us_x_max = 5.0; + us_y_min = 10.0; + us_y_max = 80.0; + us_z_min = 370.0; + us_z_max = 480.0; + + centroid_min_valid_station = 0; + } + else if (option == Option::test_beam_2024) + { + scifi_shower_window_width = 128; + scifi_min_hits_in_window = 36; + scifi_min_n_hits_for_centroid = 0; + scifi_qdc_to_gev = 0.14; + + scifi_x_min = -47.0; + scifi_x_max = -27.0; + scifi_y_min = 35.0; + scifi_y_max = 55.0; + scifi_z_min = 310.0; + scifi_z_max = 380.0; + + us_x_min = std::nan(""); + us_x_max = std::nan(""); + us_y_min = std::nan(""); + us_y_max = std::nan(""); + us_z_min = std::nan(""); + us_z_max = std::nan(""); + + centroid_min_valid_station = 0; + } + else + { + throw std::invalid_argument("Unknown configuration option"); + } +} + +snd::Configuration::Option snd::Configuration::GetOption(int run_number) +{ + if (run_number >= 100840) { + std::cout << "Choosing option >>>>>>>>>>\t test_beam_2024 \t<<<<<<<<<<" <= 100000) { + std::cout << "Choosing option >>>>>>>>>>\t test_beam_2023 \t<<<<<<<<<<" <>>>>>>>>>\t ti18_2022_2023 \t<<<<<<<<<<" <>>>>>>>>>\t ti18_2024_2025 \t<<<<<<<<<<" < + +#include "TClonesArray.h" +#include "Scifi.h" +#include "MuFilter.h" +#include "sndConfiguration.h" +#include "sndScifiPlane.h" +#include "sndUSPlane.h" +#include "sndScifiHit.h" +#include "MuFilterHit.h" + +std::vector snd::analysis_tools::FillScifi(const snd::Configuration &configuration, TClonesArray *sf_hits, Scifi *scifi_geometry) +{ + + std::vector scifi_planes; + + int begin{0}; + int count{0}; + + int n_sf_hits{sf_hits->GetEntries()}; + + for (int st{1}; st <= configuration.scifi_n_stations; ++st) + { + begin = count; + while (count < n_sf_hits && + st == static_cast(sf_hits->At(count))->GetStation()) + { + ++count; + } + scifi_planes.emplace_back(snd::analysis_tools::ScifiPlane(sf_hits, configuration, scifi_geometry, begin, count, st)); + } + return scifi_planes; +} + +std::vector snd::analysis_tools::FillUS(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry) +{ + + std::vector us_planes; + + int begin{0}; + int count{0}; + + int n_mufi_hits{mufi_hits->GetEntries()}; + // skip veto/beam monitor + while (count < n_mufi_hits && + static_cast(mufi_hits->At(count))->GetSystem() != 2) + { + ++count; + } + // plane count starts from 0 + for (int pl{0}; pl < configuration.us_n_stations; ++pl) + { + begin = count; + while (count < n_mufi_hits && + pl == static_cast(mufi_hits->At(count))->GetPlane() && + static_cast(mufi_hits->At(count))->GetSystem() == 2) // stop before DS + { + ++count; + } + us_planes.emplace_back(snd::analysis_tools::USPlane(mufi_hits, configuration, mufilter_geometry, begin, count, pl + 1)); + } + return us_planes; +} \ No newline at end of file diff --git a/analysis/tools/sndPlaneTools.h b/analysis/tools/sndPlaneTools.h new file mode 100644 index 0000000000..3b3ee9fd66 --- /dev/null +++ b/analysis/tools/sndPlaneTools.h @@ -0,0 +1,21 @@ +#ifndef SND_PLANETOOLS_H +#define SND_PLANETOOLS_H + +#include + +#include "TClonesArray.h" +#include "Scifi.h" +#include "MuFilter.h" +#include "sndConfiguration.h" +#include "sndScifiPlane.h" +#include "sndUSPlane.h" + +namespace snd { + namespace analysis_tools { + // Produce scifi and us planes from data + std::vector FillScifi(const Configuration &configuration, TClonesArray *sf_hits, Scifi *scifi_geometry); + std::vector FillUS(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry); + } +} + +#endif \ No newline at end of file diff --git a/analysis/tools/sndScifiPlane.cxx b/analysis/tools/sndScifiPlane.cxx new file mode 100644 index 0000000000..ef3cf80420 --- /dev/null +++ b/analysis/tools/sndScifiPlane.cxx @@ -0,0 +1,278 @@ +#include "sndScifiPlane.h" + +#include +#include +#include +#include +#include + +#include "TClonesArray.h" +#include "TVector3.h" +#include "Scifi.h" +#include "sndScifiHit.h" + +snd::analysis_tools::ScifiPlane::ScifiPlane(TClonesArray *snd_hits, snd::Configuration configuration, Scifi *scifi_geometry, int index_begin, int index_end, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""), std::nan("")), station_(station) +{ + if (index_begin > index_end) + { + throw std::runtime_error{"Begin index > end index"}; + } + + for (int i{index_begin}; i < index_end; ++i) + { + auto snd_hit = static_cast(snd_hits->At(i)); + ScifiHit hit; + hit.channel_index = 512 * snd_hit->GetMat() + 64 * snd_hit->GetTofpetID(0) + 63 - snd_hit->Getchannel(0); + hit.timestamp = snd_hit->GetTime(0); + hit.qdc = snd_hit->GetSignal(0); + hit.is_x = snd_hit->isVertical(); + + TVector3 A, B; + int detectorID = snd_hit->GetDetectorID(); + scifi_geometry->GetSiPMPosition(detectorID, A, B); + hit.z = A.Z(); + if (hit.is_x) + { + hit.x = A.X(); + hit.y = std::nan(""); + } + else + { + hit.x = std::nan(""); + hit.y = A.Y(); + } + hits_.push_back(hit); + } +} + +const snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPlane::GetNHits() const +{ + xy_pair counts{0, 0}; + counts.x = std::count_if(hits_.begin(), hits_.end(), [](auto &hit) + { return hit.is_x; }); + counts.y = hits_.size() - counts.x; + + return counts; +} + +bool snd::analysis_tools::ScifiPlane::IsShower() const +{ + if (configuration_.scifi_min_hits_in_window > configuration_.scifi_shower_window_width) + { + throw std::runtime_error{"min_hits > window_width"}; + } + + xy_pair> is_hit; + is_hit.x.resize(configuration_.scifi_n_channels_per_plane, 0); + is_hit.y.resize(configuration_.scifi_n_channels_per_plane, 0); + + for (auto &hit : hits_) + { + (hit.is_x ? is_hit.x : is_hit.y)[hit.channel_index] = 1; + } + + auto density = [&](std::vector &hit_arr) + { + int count{0}; + + // Initial count for the first window + for (int i{0}; i < configuration_.scifi_shower_window_width; ++i) + { + count += hit_arr[i]; + } + + if (count >= configuration_.scifi_min_hits_in_window) + return true; + + // Slide the window across the array + for (int i = configuration_.scifi_shower_window_width; i < configuration_.scifi_n_channels_per_plane; ++i) + { + count += hit_arr[i] - hit_arr[i - configuration_.scifi_shower_window_width]; + if (count >= configuration_.scifi_min_hits_in_window) + return true; + } + + return false; + }; + + return density(is_hit.x) && density(is_hit.y); +} + +const TVector3 snd::analysis_tools::ScifiPlane::GetCluster(int max_gap) const +{ + std::vector pos_x(configuration_.scifi_n_channels_per_plane, std::nan("")); + std::vector pos_y(configuration_.scifi_n_channels_per_plane, std::nan("")); + + for (auto &hit : hits_) + { + if (hit.qdc > 0) + { + if (hit.is_x) + { + pos_x[hit.channel_index] = hit.x; + } + else + { + pos_y[hit.channel_index] = hit.y; + } + } + } + + auto largest_cluster = [&](const std::vector &positions) + { + int n = positions.size(); + + int best_start = -1, best_end = -1, best_size = 0; + int start = -1, gap_count = 0, size = 0; + + // Find the largest cluster + for (int i = 0; i < n; ++i) + { + if (!std::isnan(positions[i])) + { + if (start == -1) + start = i; // Start a new cluster + size++; + gap_count = 0; // Reset consecutive gap counter + } + else + { + gap_count++; + if (gap_count > max_gap) + { // Too many consecutive gaps, finalize previous cluster + if (size > best_size) + { + best_start = start; + best_end = i - gap_count; // End before the excessive gaps + best_size = size; + } + // Reset for a new potential cluster + start = -1; + gap_count = 0; + size = 0; + } + else + { + size++; // Gaps within max_gap still count in cluster size + } + } + } + + // Check last cluster + if (size > best_size) + { + best_start = start; + best_end = n - 1; + best_size = size; + } + + // Compute the average of non-gap values in the best cluster + if (best_start == -1 || best_end == -1) + return std::nan(""); // No valid cluster found + + double sum = 0.0; + int count = 0; + for (int i = best_start; i <= best_end; ++i) + { + if (!std::isnan(positions[i])) + { + sum += positions[i]; + count++; + } + } + + return (count > 0) ? sum / count : std::nan(""); + }; + + double cluster_x = largest_cluster(pos_x); + double cluster_y = largest_cluster(pos_y); + if (!(std::isnan(cluster_x) || std::isnan(cluster_y))) { + return TVector3(cluster_x, cluster_y, hits_[0].z); + } + return TVector3(std::nan(""), std::nan(""), std::nan("")); +} + +void snd::analysis_tools::ScifiPlane::TimeFilter(double min_timestamp, double max_timestamp) +{ + hits_.erase(std::remove_if(hits_.begin(), hits_.end(), + [&](auto &hit) + { return hit.timestamp < min_timestamp || hit.timestamp > max_timestamp; }), + hits_.end()); +} + +snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPlane::GetPointQdc(const TVector3 &point, double radius) const +{ + xy_pair qdc{0.0, 0.0}; + for (const auto &hit : hits_) { + if (hit.is_x && std::abs(hit.x - point.X()) <= radius) { + qdc.x += hit.qdc; + } + else if (!hit.is_x && std::abs(hit.y - point.Y()) <= radius) { + qdc.y += hit.qdc; + } + } + return qdc; +} + +void snd::analysis_tools::ScifiPlane::FindCentroid() +{ + centroid_.SetXYZ(0, 0, 0); + double tot_qdc_x{0}; + double tot_qdc_y{0}; + std::vector cleaned_hits = hits_; + + cleaned_hits.erase(std::remove_if(cleaned_hits.begin(), cleaned_hits.end(), + [&](auto &hit) + { return hit.qdc <= 0; }), + cleaned_hits.end()); + int counts_x = std::count_if(cleaned_hits.begin(), cleaned_hits.end(), [](auto &hit) + { return hit.is_x; }); + int counts_y = cleaned_hits.size()-counts_x; + if (counts_x < configuration_.scifi_min_n_hits_for_centroid && counts_y < configuration_.scifi_min_n_hits_for_centroid ) { + centroid_.SetXYZ(std::nan(""), std::nan(""), std::nan("")); + return; + } + + for (auto &hit : cleaned_hits) + { + if (hit.qdc > 0) + { + if (hit.is_x) + { + centroid_.SetX(centroid_.X() + hit.x * hit.qdc); + tot_qdc_x += hit.qdc; + } + else + { + centroid_.SetY(centroid_.Y() + hit.y * hit.qdc); + tot_qdc_y += hit.qdc; + } + + centroid_.SetZ(centroid_.Z() + hit.z * hit.qdc); + } + } + centroid_.SetXYZ((tot_qdc_x > 0) ? centroid_.X() / tot_qdc_x : std::nan(""), (tot_qdc_y > 0) ? centroid_.Y() / tot_qdc_y : std::nan(""), (tot_qdc_x+tot_qdc_y > 0) ? centroid_.Z() / (tot_qdc_x+tot_qdc_y) : std::nan("")); + centroid_error_.SetXYZ(configuration_.scifi_centroid_error_x, configuration_.scifi_centroid_error_y, configuration_.scifi_centroid_error_z); +} + +const snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPlane::GetTotQdc(bool only_positive) const +{ + xy_pair qdc_sum{0.0, 0.0}; + qdc_sum.x = std::accumulate(hits_.begin(), hits_.end(), 0.0, [&](double current_sum, auto &hit) + { return (hit.is_x && (hit.qdc > 0 || !only_positive)) ? (current_sum + hit.qdc) : current_sum; }); + qdc_sum.y = std::accumulate(hits_.begin(), hits_.end(), 0.0, [&](double current_sum, auto &hit) + { return (!hit.is_x && (hit.qdc > 0 || !only_positive)) ? (current_sum + hit.qdc) : current_sum; }); + + return qdc_sum; +} + +const snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPlane::GetTotEnergy(bool only_positive) const +{ + xy_pair energy{0.0, 0.0}; + auto qdc = GetTotQdc(only_positive); + + energy.x = qdc.x * configuration_.scifi_qdc_to_gev; + energy.y = qdc.y * configuration_.scifi_qdc_to_gev; + + return energy; +} \ No newline at end of file diff --git a/analysis/tools/sndScifiPlane.h b/analysis/tools/sndScifiPlane.h new file mode 100644 index 0000000000..884406ab47 --- /dev/null +++ b/analysis/tools/sndScifiPlane.h @@ -0,0 +1,64 @@ +#ifndef SND_SCIFIPLANE_H +#define SND_SCIFIPLANE_H + +#include + +#include "TVector3.h" +#include "TClonesArray.h" +#include "Scifi.h" +#include "sndConfiguration.h" + +namespace snd { + namespace analysis_tools { + class ScifiPlane + { + public: + // x and y planes + template + struct xy_pair + { + T x{}; + T y{}; + }; + + struct ScifiHit + { + double qdc{}; + double timestamp{}; + double x{}; + double y{}; + double z{}; + int channel_index{}; + bool is_x{}; + }; + + ScifiPlane(TClonesArray *snd_hits, Configuration configuration, Scifi *scifi_geometry, int index_begin, int index_end, int station); + + const int GetStation() const { return station_; }; + const std::vector GetHits() const { return hits_; }; + const TVector3 GetCentroid() const { return centroid_; }; + const TVector3 GetCentroidError() const { return centroid_error_; } + const xy_pair GetTotQdc(bool only_positive = false) const; + const xy_pair GetTotEnergy(bool only_positive = false) const; + const xy_pair GetNHits() const; + // Position of larger cluster of consecutive hits, allowing at most max_gap channels with no hit + const TVector3 GetCluster(int max_gap) const; + // The centroid is the qdc-weighted mean of hit positions, considering only hits with positive qdc + void FindCentroid(); + bool IsShower() const; + void TimeFilter(double min_timestamp, double max_timestamp); + // qdc from hits within a given point and radius (square, not circle) + xy_pair GetPointQdc(const TVector3 &point, double radius) const; + + private: + std::vector hits_; + Configuration configuration_; + TVector3 centroid_; + TVector3 centroid_error_; + + int station_; + }; + } +} + +#endif \ No newline at end of file diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx new file mode 100644 index 0000000000..0492ed5235 --- /dev/null +++ b/analysis/tools/sndUSPlane.cxx @@ -0,0 +1,178 @@ +#include "sndUSPlane.h" + +#include +#include +#include +#include + +#include "TClonesArray.h" +#include "TVector3.h" +#include "MuFilter.h" +#include "MuFilterHit.h" + +snd::analysis_tools::USPlane::USPlane(TClonesArray *snd_hits, snd::Configuration configuration, MuFilter *muon_filter_geometry, int index_begin, int index_end, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) +{ + if (index_begin > index_end) + { + throw std::runtime_error{"Begin index > end index"}; + } + for (int j{index_begin}; j < index_end; ++j) + { + auto mu_hit = static_cast(snd_hits->At(j)); + + for (int i{0}; i < 16; ++i) + { + if (mu_hit->isMasked(i) || mu_hit->GetSignal(i) < -990.) continue; + USHit hit; + hit.bar = static_cast(mu_hit->GetDetectorID() % 1000); + hit.channel_index = 16 * hit.bar + i; + hit.timestamp = mu_hit->GetTime(i); + hit.qdc = mu_hit->GetSignal(i); + hit.is_large = !mu_hit->isShort(i); + + TVector3 A, B; + int detectorID = mu_hit->GetDetectorID(); + muon_filter_geometry->GetPosition(detectorID, A, B); + hit.is_right = i > 7 ? true : false; + hit.x = hit.is_right ? B.X() : A.X(); + hit.y = A.Y(); + hit.z = A.Z(); + hits_.push_back(hit); + } + } +} + +void snd::analysis_tools::USPlane::FindCentroid() +{ + // min number of hit in the plane to attempt to find a centroid + if (hits_.size() < configuration_.us_min_n_hits_for_centroid) + { + // std::cout<<"Not enough hits in US plane " << station_ <<" to find centroid\n"; + return; + } + + double total_qdc = GetTotQdc().large; + if (total_qdc > 0.0) + { + for (const auto &hit : hits_) + { + // weigthed sum + double weighted_sum_x{0.0}, weighted_sum_y{0.0}, weighted_sum_z{0.0}; + double total_qdc_positive{0.0}; + + if (hit.qdc > 0.0) + { + weighted_sum_x += hit.x * hit.qdc; + weighted_sum_y += hit.y * hit.qdc; + weighted_sum_z += hit.z * hit.qdc; + total_qdc_positive += hit.qdc; + } + + double x = weighted_sum_x / total_qdc_positive; + double y = weighted_sum_y / total_qdc_positive; + double z = weighted_sum_z / total_qdc_positive; + centroid_.SetXYZ(x, y, z); + } + } + centroid_error_.SetXYZ(configuration_.us_centroid_error_x, configuration_.us_centroid_error_y, configuration_.us_centroid_error_z); +} + +const snd::analysis_tools::USPlane::sl_pair snd::analysis_tools::USPlane::GetTotQdc() const +{ + sl_pair totQdc{0.0, 0.0}; + for (const auto &hit : hits_) + { + if (hit.is_large) + totQdc.large += hit.qdc; + else + totQdc.small += hit.qdc; + } + return totQdc; +} + +const snd::analysis_tools::USPlane::sl_pair snd::analysis_tools::USPlane::GetTotEnergy() const +{ + sl_pair tot_energy{0.0, 0.0}; + sl_pair tot_qdc = GetTotQdc(); + + tot_energy.large = tot_qdc.large *configuration_.us_qdc_to_gev; + tot_energy.small = tot_qdc.small *configuration_.us_qdc_to_gev; + + return tot_energy; +} + + +const snd::analysis_tools::USPlane::rl_pair snd::analysis_tools::USPlane::GetSideQdc() const +{ + rl_pair side_qdc{0.0, 0.0}; + for (const auto &hit : hits_) + { + if (hit.is_large) + { + if (hit.is_right) + side_qdc.right += hit.qdc; + else + side_qdc.left += hit.qdc; + } + } + return side_qdc; +} + +const snd::analysis_tools::USPlane::rl_pair snd::analysis_tools::USPlane::GetBarQdc(int bar_to_compute) const +{ + rl_pair bar_qdc{0.0, 0.0}; + for (const auto &hit : hits_) + { + if (hit.bar != bar_to_compute) + continue; + else + { + if (hit.is_large) + { + if (hit.is_right) + bar_qdc.right += hit.qdc; + else + bar_qdc.left += hit.qdc; + } + } + } + return bar_qdc; +} + +const snd::analysis_tools::USPlane::sl_pair snd::analysis_tools::USPlane::GetBarNHits(int bar_to_compute) const +{ + sl_pair bar_hit{0, 0}; + for (const auto &hit : hits_) + { + if (hit.bar != bar_to_compute) + continue; + else + { + if (hit.is_large) + + bar_hit.large++; + else + bar_hit.small++; + + } + } + return bar_hit; +} + +void snd::analysis_tools::USPlane::TimeFilter(double min_timestamp, double max_timestamp) +{ + hits_.erase(std::remove_if(hits_.begin(), hits_.end(), + [&](auto &hit) + { return hit.timestamp < min_timestamp || hit.timestamp > max_timestamp; }), + hits_.end()); +} + +const snd::analysis_tools::USPlane::sl_pair snd::analysis_tools::USPlane::GetNHits() const +{ + sl_pair counts{0, 0}; + counts.large = std::count_if(hits_.begin(), hits_.end(), [](auto &hit) + { return hit.is_large; }); + counts.small = hits_.size() - counts.large; + + return counts; +} \ No newline at end of file diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h new file mode 100644 index 0000000000..1ed3e26a8a --- /dev/null +++ b/analysis/tools/sndUSPlane.h @@ -0,0 +1,76 @@ +#ifndef SND_USPLANE_H +#define SND_USPLANE_H + +#include + +#include "TVector3.h" +#include "TClonesArray.h" +#include "MuFilter.h" +#include "sndConfiguration.h" + +namespace snd { + namespace analysis_tools { + class USPlane + { + public: + // small and large SiPMs + template + struct sl_pair + { + T small{}; + T large{}; + }; + + // right and left side of US bars + template + struct rl_pair + { + T right{}; + T left{}; + }; + + // hits vector, each hit has info about timestamp, qdc and position + struct USHit + { + int channel_index; + int bar; + + double qdc; + double timestamp; + double x; // position of right or left side of bar + double y; + double z; + + bool is_large; + bool is_right; + }; + + USPlane(TClonesArray *snd_hits, Configuration configuration, MuFilter *muon_filter_geometry, int index_begin, int index_end, int station); + + const sl_pair GetNHits() const; + const int GetStation() const { return station_; }; + + const sl_pair GetTotQdc() const; + const sl_pair GetTotEnergy() const; + const rl_pair GetSideQdc() const; + const rl_pair GetBarQdc(int bar_to_compute) const; + const sl_pair GetBarNHits(int bar_to_compute) const; + const std::vector GetHits() const { return hits_; }; + // The centroid is the qdc-weighted mean of hit positions, considering only hits with positive qdc + void FindCentroid(); + const TVector3 GetCentroid() const { return centroid_; }; + const TVector3 GetCentroidError() const { return centroid_error_; }; + + void TimeFilter(double min_timestamp, double max_timestamp); + + private: + std::vector hits_; + Configuration configuration_; + TVector3 centroid_; + TVector3 centroid_error_; + int station_; + }; + } +} + +#endif \ No newline at end of file From 3bc90c3ff9045b1993fd2a9795aabdd6987ac016 Mon Sep 17 00:00:00 2001 From: Ridz31 Date: Thu, 19 Jun 2025 12:15:28 +0200 Subject: [PATCH 12/99] Add the timing correction to the hit timing and replace C-style casts with static_cast --- analysis/tools/sndSciFiTools.cxx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/analysis/tools/sndSciFiTools.cxx b/analysis/tools/sndSciFiTools.cxx index 8405243c90..53ebc73cac 100644 --- a/analysis/tools/sndSciFiTools.cxx +++ b/analysis/tools/sndSciFiTools.cxx @@ -9,6 +9,7 @@ #include "sndScifiHit.h" #include "ROOT/TSeq.hxx" #include "Scifi.h" +#include "TROOT.h" void snd::analysis_tools::getSciFiHitsPerStation(const TClonesArray *digiHits, std::vector &horizontal_hits, std::vector &vertical_hits) @@ -132,8 +133,10 @@ float snd::analysis_tools::peakScifiTiming(const TClonesArray &digiHits, int bin TH1F ScifiTiming("Timing", "Scifi Timing", bins, min_x, max_x); - int refStation = ((sndScifiHit *)digiHits.At(0))->GetStation(); - bool refOrientation = ((sndScifiHit *)digiHits.At(0))->isVertical(); + Scifi *ScifiDet = dynamic_cast (gROOT->GetListOfGlobals()->FindObject("Scifi") ); + auto* hit = static_cast(digiHits[0]); + int refStation = hit->GetStation(); + bool refOrientation = hit->isVertical(); float hitTime = -1.0; float timeConversion = 1.; if (!isMC) { @@ -146,6 +149,10 @@ float snd::analysis_tools::peakScifiTiming(const TClonesArray &digiHits, int bin continue; } hitTime = hit->GetTime() * timeConversion; + if (!isMC){ + int id_hit = hit ->GetDetectorID(); + hitTime = ScifiDet->GetCorrectedTime(id_hit, hitTime, 0); + } if (hitTime < min_x || hitTime > max_x) { continue; } @@ -596,4 +603,4 @@ snd::analysis_tools::findCentreOfGravityPerStation(const TClonesArray* digiHits, } double meanY = computeMean(y_positions); return {meanX, meanY}; -} \ No newline at end of file +} From 687dfd0bcb98c4da27199ac50e5d9246fa3ecc57 Mon Sep 17 00:00:00 2001 From: Ridz31 Date: Thu, 24 Jul 2025 15:11:28 +0200 Subject: [PATCH 13/99] Introduced hitPositionVectorsPerStation() and implemented RRangeCast in hitPositionVectorsPerStation() --- analysis/tools/sndSciFiTools.cxx | 44 ++++++++++++++++++++++++++++++++ analysis/tools/sndSciFiTools.h | 5 ++++ 2 files changed, 49 insertions(+) diff --git a/analysis/tools/sndSciFiTools.cxx b/analysis/tools/sndSciFiTools.cxx index 53ebc73cac..eb3bf95dec 100644 --- a/analysis/tools/sndSciFiTools.cxx +++ b/analysis/tools/sndSciFiTools.cxx @@ -10,6 +10,7 @@ #include "ROOT/TSeq.hxx" #include "Scifi.h" #include "TROOT.h" +#include "ROOT/RRangeCast.hxx" void snd::analysis_tools::getSciFiHitsPerStation(const TClonesArray *digiHits, std::vector &horizontal_hits, std::vector &vertical_hits) @@ -604,3 +605,46 @@ snd::analysis_tools::findCentreOfGravityPerStation(const TClonesArray* digiHits, double meanY = computeMean(y_positions); return {meanX, meanY}; } + +std::pair,std::vector> snd::analysis_tools::hitPositionVectorsPerStation(const TClonesArray *digiHits, int station, Scifi* ScifiDet){ + /*This function returns the hit vector position for the digiHits in both orientations + Arguments: + digiHits: A TClonesArray containing the hits in the SciFi detector. + station: The station number for which to retrieve the hit positions. + ScifiDet: A pointer to the Scifi detector object to retrieve SiPM positions. + Returns: + A pair of vectors containing the x and y positions of the hits in the specified station. + If no hits are found, it returns empty vectors and logs an error message. + */ + + if (!digiHits) { + LOG(ERROR) << "Error: digiHits is null"; + } + std::vector x_positions; + std::vector y_positions; + + TVector3 A, B; + for (auto* hit : ROOT::RRangeCast(*digiHits)) { + if (!hit || !hit->isValid()) { + continue; + } + if (hit->GetStation() != station) { + continue; + } + ScifiDet->GetSiPMPosition(hit->GetDetectorID(), A, B); + if (hit->isVertical()) { + x_positions.push_back((A.X() + B.X()) * 0.5); + } + else { + y_positions.push_back((A.Y() + B.Y()) * 0.5); + } + } + if (x_positions.empty()) { + LOG(ERROR) << "Error: No hits enter."; + } + if (y_positions.empty()) { + LOG(ERROR) << "Error: No hits enter."; + } + return {x_positions, y_positions}; +} + diff --git a/analysis/tools/sndSciFiTools.h b/analysis/tools/sndSciFiTools.h index 9281ee0ebe..090e63012a 100644 --- a/analysis/tools/sndSciFiTools.h +++ b/analysis/tools/sndSciFiTools.h @@ -79,5 +79,10 @@ namespace snd { // Find the Center of Particle Showering on the SciFi plane std::pair findCentreOfGravityPerStation(const TClonesArray* digiHits, int station, Scifi* ScifiDet); + + // Function to get the hit position vectors for a specific station + // Returns a pair of vectors containing the x and y positions of the hits in the specified station + std::pair, std::vector> hitPositionVectorsPerStation(const TClonesArray* digiHits, int station, Scifi* ScifiDet); + } } From f60cbc6a60eefad0a34db8daebe05fab130e2bb8 Mon Sep 17 00:00:00 2001 From: Ridz31 Date: Thu, 24 Jul 2025 15:21:23 +0200 Subject: [PATCH 14/99] Utilised the hitPositionVectorsPerStation() in findCentreOfGravityPerStation() --- analysis/tools/sndSciFiTools.cxx | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/analysis/tools/sndSciFiTools.cxx b/analysis/tools/sndSciFiTools.cxx index eb3bf95dec..22753572fb 100644 --- a/analysis/tools/sndSciFiTools.cxx +++ b/analysis/tools/sndSciFiTools.cxx @@ -576,25 +576,9 @@ snd::analysis_tools::findCentreOfGravityPerStation(const TClonesArray* digiHits, if (!digiHits) { LOG(ERROR) << "Error: digiHits is null in findCentreOfGravityPerStation"; } - std::vector x_positions; - std::vector y_positions; - TVector3 A, B; - for (auto* obj : *digiHits) { - auto* hit = dynamic_cast(obj); - if (!hit || !hit->isValid()) { - continue; - } - if (hit->GetStation() != station) { - continue; - } - ScifiDet->GetSiPMPosition(hit->GetDetectorID(), A, B); - if (hit->isVertical()) { - x_positions.push_back((A.X() + B.X()) * 0.5); - } - else { - y_positions.push_back((A.Y() + B.Y()) * 0.5); - } - } + + auto [x_positions, y_positions] = snd::analysis_tools::hitPositionVectorsPerStation(digiHits, station, ScifiDet); + if (x_positions.empty()) { LOG(ERROR) << "Error: No hits enter."; } @@ -647,4 +631,5 @@ std::pair,std::vector> snd::analysis_tools::hitPosit } return {x_positions, y_positions}; } + From 2c774bf05477730dcd4fbc9f2dd70baf21b29c9b Mon Sep 17 00:00:00 2001 From: Ridz31 Date: Thu, 24 Jul 2025 15:25:52 +0200 Subject: [PATCH 15/99] Introduced the Summed Hit Density Weights to sndSciFiTools --- analysis/tools/sndSciFiTools.cxx | 95 ++++++++++++++++++++++++++++++++ analysis/tools/sndSciFiTools.h | 3 + 2 files changed, 98 insertions(+) diff --git a/analysis/tools/sndSciFiTools.cxx b/analysis/tools/sndSciFiTools.cxx index 22753572fb..3709bcd046 100644 --- a/analysis/tools/sndSciFiTools.cxx +++ b/analysis/tools/sndSciFiTools.cxx @@ -633,3 +633,98 @@ std::pair,std::vector> snd::analysis_tools::hitPosit } +double hitWeightComputation(std::vector hit_position) { + /* This function returns the summation of the hit weights + where a hit weight is the number of neighbouring hits within 1 cm position (default width). + + Arguments: + hit_position: A vector containing the positions of the hits in a specific SciFi station. + + Returns: + The sum of the weights of the hits in the vector. + Returns 0 if the vector is empty or if the sum of weights is 0. + */ + + if (hit_position.empty()) { + LOG(INFO) << "Warning: The hit position vector is empty." << std::endl; + return 0; // Return 0 if the vector is empty + } + + int n_hits = hit_position.size(); + double width = 1.0; // Width around the hit to consider as a neighbour, in cm + std::vector weights; // Vector to store the weights of each hit + + // Sorting the hits for efficient neighbour counting + std::sort(hit_position.begin(), hit_position.end()); + + // Initialize pointers for the sliding window. + // Both pointers will only move forward (or stay put) across the outer loop iterations. + int left_ptr = 0; + int right_ptr = 0; + + // Loop through each hit position to calculate its neighbour count + for (int i = 0; i < n_hits; i++) { + double specific_hit = hit_position[i]; + + // Move right_ptr forward to include all hits within (specific_hit + width). + // It will point to the first element *just outside* the right boundary of the window. + while (right_ptr < n_hits && hit_position[right_ptr] <= specific_hit + width) { + right_ptr++; + } + + // Move left_ptr forward to exclude all hits *less than* (specific_hit - width). + // It will point to the first element *just inside* or at the left boundary of the window. + while (left_ptr < n_hits && hit_position[left_ptr] < specific_hit - width) { + left_ptr++; + } + + // Calculate neighbouring hits within the window: + // The number of hits in the current window is (right_ptr - left_ptr). + // This count includes the 'specific_hit' itself. + double count_in_window = right_ptr - left_ptr; + + // Subtract 1 to exclude the 'specific_hit' itself from the neighbour count. + // We must ensure that 'specific_hit' (hit_position[i]) is actually within the window + // defined by left_ptr and right_ptr. Since the array is sorted and we are iterating + // through 'i', hit_position[i] will always be between hit_position[left_ptr] and + // hit_position[right_ptr-1] (if left_ptr <= i < right_ptr). + double neighbour_no_of_hits = count_in_window - 1; + + // Ensure the neighbour count is non-negative + if (neighbour_no_of_hits < 0) { + neighbour_no_of_hits = 0; + } + + weights.push_back(neighbour_no_of_hits); + } + + // Calculate the sum of all computed weights + double sum_weights = std::accumulate(weights.begin(), weights.end(), 0.0); + + return sum_weights; +} + +std::pair snd::analysis_tools::hitDensityPerStation(const TClonesArray *digiHits, int station, Scifi* ScifiDet){ + /* + This function returns the hit density which is described as the summation of the hit weights + where a hit weigth is the number of neighbouring hits within 1 cm postion (default width) + arguments: hit position vector : vector ontaining the positions of the hits in a specific SciFi station + returns: the sum of the weights of the hits in the vector + If the vector is empty, it returns 0. + If the sum of weights is 0, it returns 0. + */ + std::pair, std::vector> hit_position_vec = hitPositionVectorsPerStation(digiHits, station, ScifiDet); + std::vector hit_position_x = hit_position_vec.first; // Assuming we are interested in X positions + std::vector hit_position_y = hit_position_vec.second; // Assuming we are interested in Y positions + + if (hit_position_x.size()==0 && hit_position_y.size()==0) { + LOG(INFO)<< "Warning: The hit position vector is empty." << std::endl; + return {0.0,0.0}; // Check if the vector is empty + } + + double sum_weights_x = hitWeightComputation(hit_position_x); + double sum_weights_y = hitWeightComputation(hit_position_y); + + return {sum_weights_x, sum_weights_y}; +} + diff --git a/analysis/tools/sndSciFiTools.h b/analysis/tools/sndSciFiTools.h index 090e63012a..fec3ded4e4 100644 --- a/analysis/tools/sndSciFiTools.h +++ b/analysis/tools/sndSciFiTools.h @@ -84,5 +84,8 @@ namespace snd { // Returns a pair of vectors containing the x and y positions of the hits in the specified station std::pair, std::vector> hitPositionVectorsPerStation(const TClonesArray* digiHits, int station, Scifi* ScifiDet); + // Function to compute the hit density per station + // Returns a pair of doubles containing the hit density for horizontal and vertical orientations of that station + std::pair hitDensityPerStation(const TClonesArray* digiHits, int station, Scifi* ScifiDet); } } From 3fd4ed80970e616826fa7893134aaac1253bfaf2 Mon Sep 17 00:00:00 2001 From: fmei Date: Thu, 14 Aug 2025 19:08:57 +0200 Subject: [PATCH 16/99] fix class linking --- analysis/tools/AnalysisToolsLinkDef.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/analysis/tools/AnalysisToolsLinkDef.h b/analysis/tools/AnalysisToolsLinkDef.h index bea0805feb..da68652ac9 100644 --- a/analysis/tools/AnalysisToolsLinkDef.h +++ b/analysis/tools/AnalysisToolsLinkDef.h @@ -7,9 +7,9 @@ #pragma link C++ nestedclasses; #pragma link C++ nestedtypedef; -#pragma link C++ class snd::sndConfiguration+; -#pragma link C++ class snd::analysis_tools::sndScifiPlane+; -#pragma link C++ class snd::analysis_tools::sndUSPlane+; +#pragma link C++ class snd::Configuration+; +#pragma link C++ class snd::analysis_tools::ScifiPlane+; +#pragma link C++ class snd::analysis_tools::USPlane+; #pragma link C++ namespace snd::analysis_tools; #pragma link C++ defined_in namespace snd::analysis_tools; From c0fe18fad8300fa46a7bfee53733f20e0652994d Mon Sep 17 00:00:00 2001 From: fmei Date: Mon, 18 Aug 2025 15:00:46 +0200 Subject: [PATCH 17/99] use const & --- analysis/tools/AnalysisToolsLinkDef.h | 4 ++-- analysis/tools/sndGeometryGetter.cxx | 2 +- analysis/tools/sndGeometryGetter.h | 2 +- analysis/tools/sndTchainGetter.cxx | 2 +- analysis/tools/sndTchainGetter.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/analysis/tools/AnalysisToolsLinkDef.h b/analysis/tools/AnalysisToolsLinkDef.h index da68652ac9..1bb1d1b467 100644 --- a/analysis/tools/AnalysisToolsLinkDef.h +++ b/analysis/tools/AnalysisToolsLinkDef.h @@ -34,9 +34,9 @@ #pragma link C++ function snd::analysis_tools::showerInteractionWall(const TClonesArray &, int, std::string); #pragma link C++ function snd::analysis_tools::GetDataBasePath(const std::string &, int); #pragma link C++ function snd::analysis_tools::GetTChain(const std::string &, int, int); -#pragma link C++ function snd::analysis_tools::GetTChain(std::string); +#pragma link C++ function snd::analysis_tools::GetTChain(const std::string &); #pragma link C++ function snd::analysis_tools::GetGeoPath(const std::string &,int); -#pragma link C++ function snd::analysis_tools::GetGeometry(std::string); +#pragma link C++ function snd::analysis_tools::GetGeometry(const std::string &); #pragma link C++ function snd::analysis_tools::FillScifi(const snd::Configuration &, TClonesArray *, Scifi *); #pragma link C++ function snd::analysis_tools::FillUS(const snd::Configuration &, TClonesArray *, MuFilter *); diff --git a/analysis/tools/sndGeometryGetter.cxx b/analysis/tools/sndGeometryGetter.cxx index 5764f9f98d..df02ed9970 100644 --- a/analysis/tools/sndGeometryGetter.cxx +++ b/analysis/tools/sndGeometryGetter.cxx @@ -42,7 +42,7 @@ std::string snd::analysis_tools::GetGeoPath(const std::string& csv_file_path, in } // Get SciFi and MuFilter geometries -std::pair snd::analysis_tools::GetGeometry(std::string geometry_path) +std::pair snd::analysis_tools::GetGeometry(const std::string& geometry_path) { TPython::Exec("import SndlhcGeo"); TPython::Exec(("SndlhcGeo.GeoInterface('" + geometry_path + "')").c_str()); diff --git a/analysis/tools/sndGeometryGetter.h b/analysis/tools/sndGeometryGetter.h index 59d5e9e1b7..fcf2e090e0 100644 --- a/analysis/tools/sndGeometryGetter.h +++ b/analysis/tools/sndGeometryGetter.h @@ -10,7 +10,7 @@ namespace snd { namespace analysis_tools { std::string GetGeoPath(const std::string& csv_file_path, int run_number); - std::pair GetGeometry(std::string geometry_path); + std::pair GetGeometry(const std::string& geometry_path); std::pair GetGeometry(const std::string& csv_file_path, int run_number); } } diff --git a/analysis/tools/sndTchainGetter.cxx b/analysis/tools/sndTchainGetter.cxx index 6e65267a61..61677f9df0 100644 --- a/analysis/tools/sndTchainGetter.cxx +++ b/analysis/tools/sndTchainGetter.cxx @@ -52,7 +52,7 @@ std::unique_ptr snd::analysis_tools::GetTChain(const std::string& csv_fi return tchain; }; -std::unique_ptr snd::analysis_tools::GetTChain(std::string file_name){ +std::unique_ptr snd::analysis_tools::GetTChain(const std::string& file_name){ auto tchain = std::make_unique("rawConv"); tchain->Add(file_name.c_str()); return tchain; diff --git a/analysis/tools/sndTchainGetter.h b/analysis/tools/sndTchainGetter.h index ca4a0423d2..d543bdfeaa 100644 --- a/analysis/tools/sndTchainGetter.h +++ b/analysis/tools/sndTchainGetter.h @@ -9,7 +9,7 @@ namespace snd { namespace analysis_tools { std::unique_ptr GetTChain(const std::string& csv_file_path, int run_number, int n_files = -1); - std::unique_ptr GetTChain(std::string file_name); + std::unique_ptr GetTChain(const std::string& file_name); std::string GetDataBasePath(const std::string& csv_file_path, int run_number); } } From 4ea17d5f58e7503a3a88d8c18b6490b07afcb280 Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 25 Aug 2025 19:02:21 +0200 Subject: [PATCH 18/99] Update CHANGELOG before new minor release v1.1.0 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b0beb64e3..e5862420e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,13 @@ We start with the first sndsw release: v1.0.0+2025-07-updateScifi. Shall there be a strong will/need, one can go back and create and fill in the logs for previous stacks. +## v1.1.0+2025-08-BolognaTools + +### Added + +- analysys classes for SciFi and US planes, together with parameter configuraions for TI18 and testbeams setups +- SciFi tools: hit density + ## v1.0.1+2025-07-fixTB24Wtarget ### Fixed From 5d051e1d7cfcc261dea4212f9e308e2235dd38b3 Mon Sep 17 00:00:00 2001 From: siilieva Date: Sat, 30 Aug 2025 20:03:41 +0200 Subject: [PATCH 19/99] Add SciFi spatial alignment for targets 251 to 254 --- shipLHC/modifyGeoFileDict.py | 78 +++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index 573f380b28..cf3f83fb5b 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -10,7 +10,7 @@ commonPath = "/eos/experiment/sndlhc/convertedData/physics/" supportedGeoFiles = {} -supported_years = [2022, 2023, 2024] +supported_years = [2022, 2023, 2024, 2025] for year in supported_years: supportedGeoFiles["geofile_sndlhc_TI18_V0_"+str(year)+".root"] = commonPath+str(year)+"/" @@ -37,7 +37,8 @@ def modifyDicts(year=2024): mufi_spatial_aligment_consts = { 2022: [ 0.11,-0.04, 0.00, 0.10, 0.26, 0.24, 0.31, 0.34, 0.43, 1.13, 0.53, 1.31, 0.61, 1.35, 1.39], 2023: [ 0.15, 0.03, 0.00,-0.01, 0.09,-0.01, 0.06, 0.06, 0.49, 0.86, 0.20, 0.98, 0.24, 1.01, 1.11], - 2024: [-0.06, 0.04, 0.65,-0.15,-0.12,-0.26,-0.29,-0.36, 0.00, 0.35,-0.37, 0.38,-0.41, 0.30, 0.35] + 2024: [-0.06, 0.04, 0.65,-0.15,-0.12,-0.26,-0.29,-0.36, 0.00, 0.35,-0.37, 0.38,-0.41, 0.30, 0.35], + 2025: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] } mufi_spatial_aligment_keys = ['Veto1ShiftY','Veto2ShiftY','Veto3ShiftX', 'US1ShiftY','US2ShiftY','US3ShiftY','US4ShiftY','US5ShiftY', @@ -75,7 +76,8 @@ def modifyDicts(year=2024): 2023: {"t_0":0.000, "t_5478":0.082, "t_6208":0.086, "t_6443":0.082, "t_6677":0.084}, 2024: {"t_0":0.000, "t_7649":0.081, "t_8318":0.082, "t_8583":0.081, "t_8942":0.080, "t_9156":0.083, "t_9286":0.082, "t_9379":0.083, "t_9462":0.083, "t_9613":0.082, - "t_9692":0.078, "t_9882":0.084, "t_10012":0.085} + "t_9692":0.078, "t_9882":0.084, "t_10012":0.085}, + 2025: {"t_0": 0.000} } slopes_dict = ds_time_aligment_consts[year] #time delay corrections first order, only for DS at the moment @@ -189,7 +191,8 @@ def modifyDicts(year=2024): 2022: ['t_0', 't_4361','t_5117'], 2023: ['t_0', 't_5478', 't_6208', 't_6443', 't_6677'], 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', - 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'] + 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'], + 2025: ['t_0'] } scifi_time_tags = scifi_time_aligment_consts[year] for c in scifi_time_tags: @@ -525,12 +528,77 @@ def modifyDicts(year=2024): 0*u.mrad, 1.65*u.mrad, 0*u.mrad, 0*u.mrad, -0.79*u.mrad, 0*u.mrad, 0*u.mrad, 2.93*u.mrad, 0*u.mrad] + alignment['t_10423']=[ #2025 emulsion run 19 run_251 + 908.9748*u.um, 573.9855*u.um, 812.3022*u.um, + 86.541*u.um, 123.4473*u.um, 88.648*u.um, + 357.9727*u.um, 241.2571*u.um, 309.628*u.um, + 18.2807*u.um, 87.3165*u.um, 94.6991*u.um, + -1.8476*u.um, 78.9774*u.um, 13.9775*u.um, + 100.7577*u.um, -59.748*u.um, 174.542*u.um, + -100*u.um, 236.436*u.um, 196.9426*u.um, + -11.044*u.um, -185.1257*u.um, 17.2473*u.um, + -593.3074*u.um, -118.6736*u.um, -339.1444*u.um, + -101.3628*u.um, -25.2405*u.um, 184.0245*u.um, + 0*u.mrad, -0.82*u.mrad, 0*u.mrad, + 0*u.mrad, -0.73*u.mrad, 0*u.mrad, + 0*u.mrad, 0*u.mrad, 0*u.mrad, + 0*u.mrad, -0.43*u.mrad, 0*u.mrad, + 0*u.mrad, 0.12*u.mrad, 0*u.mrad] + alignment['t_11158']=[ #2025 emulsion run 20 run_252 + 758.176*u.um, 84.0408*u.um, 221.625*u.um, + 126.9696*u.um, -100.0*u.um, -542.1658*u.um, + 56.0*u.um, -234.5533*u.um, -264.4273*u.um, + 210.2911*u.um, 130.0*u.um, 9.9216*u.um, + -150.0*u.um, -102.0*u.um, -250.0*u.um, + 100.7577*u.um, -59.748*u.um, 304.0*u.um, + -200.0*u.um, 256.0*u.um, 96.9426*u.um, + -550.0*u.um, -600.0*u.um, 50*u.um, + -892.3995*u.um, -108.0*u.um, -489.6239*u.um, + -300.0*u.um, 129.0*u.um, 911.9577*u.um, + 0*u.mrad, -3.16*u.mrad, 0*u.mrad, + 0*u.mrad, -1.13*u.mrad, 0*u.mrad, + 0*u.mrad, 0*u.mrad, 0*u.mrad, + 0*u.mrad, -2.00*u.mrad, 0*u.mrad, + 0*u.mrad, 1.86*u.mrad, 0*u.mrad] + alignment['t_11576']=[ #2025 emulsion run 21 run_253 + 208.20*u.um, 125.00*u.um, 195.00*u.um, + -8.53*u.um, 44.46*u.um, 52.20*u.um, + -8.71*u.um, -30.53*u.um, -63.62*u.um, + -98.60*u.um, 10.00*u.um, -45.00*u.um, + -30.00*u.um, -8.00*u.um, -51.29*u.um, + 180.00*u.um, 98.05*u.um, 228.20*u.um, + 25.19*u.um, 67.28*u.um, 127.54*u.um, + 7.54*u.um, -141.72*u.um, -70.68*u.um, + -5.00*u.um, 0.00*u.um, -50.29*u.um, + -64.05*u.um, 86.94*u.um, 4.89*u.um, + 0.00*u.mrad, 0.01*u.mrad, -0.01*u.mrad, + -0.47*u.mrad, -0.01*u.mrad, 0.08*u.mrad, + 0.90*u.mrad, 0.05*u.mrad, 0.00*u.mrad, + -0.40*u.mrad, -0.01*u.mrad, 0.08*u.mrad, + -0.23*u.mrad, 0.01*u.mrad, 0.04*u.mrad] + alignment['t_11676']=[ #2025 emulsion run 22 run_254 + 233.2526*u.um, 203.3113*u.um, 261.1019*u.um, + -248.7328*u.um, -168.8227*u.um, -98.6278*u.um, + -238.4141*u.um, -238.2084*u.um, -260.8086*u.um, + 72.2430*u.um, 152.4410*u.um, 162.6312*u.um, + 0.0000*u.um, -37.1101*u.um, -101.2731*u.um, + 130.0000*u.um, 13.4088*u.um, 150.0951*u.um, + 223.6887*u.um, 223.8778*u.um, 254.0594*u.um, + -215.8022*u.um, -413.1773*u.um, -344.0464*u.um, + -177.8921*u.um, -214.8997*u.um, -295.3263*u.um, + 125.5983*u.um, 255.9729*u.um, 212.1852*u.um, + -0.4488*u.mrad, -0.3531*u.mrad, 0.3427*u.mrad, + 0.5384*u.mrad, 0.0341*u.mrad, 0.1195*u.mrad, + 0.7200*u.mrad, 0.2000*u.mrad, 0.0000*u.mrad, + -1.0244*u.mrad, -0.5787*u.mrad, 0.0171*u.mrad, + 0.9128*u.mrad, 0.4085*u.mrad, 0.0171*u.mrad] scifi_spatial_aligment_consts = { 2022: ['t_0', 't_4361','t_4575','t_4855','t_5172'], 2023: ['t_0', 't_5431', 't_6443', 't_6677'], 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', - 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'] + 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'], + 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676'] } scifi_spatial_tags = scifi_spatial_aligment_consts[year] for c in scifi_spatial_tags: From 609bf3e5318ed33e4f14716baa75850b1210e94b Mon Sep 17 00:00:00 2001 From: siilieva Date: Sun, 31 Aug 2025 12:26:30 +0200 Subject: [PATCH 20/99] Add SciFi time alignment for targets 251 to 254 --- shipLHC/modifyGeoFileDict.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index cf3f83fb5b..600712c623 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -186,13 +186,34 @@ def modifyDicts(year=2024): -1.119*u.ns, 0.000*u.ns, -0.005*u.ns, 0.582*u.ns, 0.875*u.ns, -0.390*u.ns, 1.059*u.ns, -0.406*u.ns, 0.000*u.ns, -1.319*u.ns, 0.337*u.ns, -2.012*u.ns, -1.059*u.ns, -0.618*u.ns, -0.825*u.ns, 0.000*u.ns, -1.205*u.ns, -0.952*u.ns, 0.241*u.ns, -1.363*u.ns, 1.123*u.ns ] +#2025 + constants['t_10423']=[ 0.000*u.ns, 0.000*u.ns, -0.337*u.ns, -0.509*u.ns, 0.125*u.ns, -0.442*u.ns, -0.049*u.ns, + -0.936*u.ns, 0.000*u.ns, 0.238*u.ns, -0.780*u.ns, -0.484*u.ns, 0.172*u.ns, -0.326*u.ns, + -1.097*u.ns, 0.000*u.ns, -0.028*u.ns, 0.543*u.ns, 0.764*u.ns, -0.568*u.ns, 0.962*u.ns, + -0.425*u.ns, 0.000*u.ns, -1.339*u.ns, 0.324*u.ns, -2.087*u.ns, -1.170*u.ns, -0.765*u.ns, + -0.768*u.ns, 0.000*u.ns, -1.312*u.ns, -1.038*u.ns, 0.204*u.ns, -1.518*u.ns, 0.960*u.ns] + constants['t_11158']=[ 0.000*u.ns, 0.000*u.ns, -0.377*u.ns, -0.542*u.ns, 0.115*u.ns, -0.456*u.ns, -0.184*u.ns, + -0.982*u.ns, 0.000*u.ns, 0.233*u.ns, -0.827*u.ns, -1.809*u.ns, 0.228*u.ns, -0.281*u.ns, + -1.140*u.ns, 0.000*u.ns, -0.020*u.ns, 0.606*u.ns, 0.813*u.ns, -0.462*u.ns, 1.016*u.ns, + -1.687*u.ns, 0.000*u.ns, -0.101*u.ns, 1.552*u.ns, -0.874*u.ns, 0.028*u.ns, 0.478*u.ns, + -0.791*u.ns, 0.000*u.ns, -1.310*u.ns, -1.055*u.ns, 0.445*u.ns, -1.393*u.ns, 0.794*u.ns] + constants['t_11576']=[ 0.000*u.ns, 0.000*u.ns, -0.373*u.ns, -0.541*u.ns, 0.143*u.ns, -0.430*u.ns, -0.117*u.ns, + -0.939*u.ns, 0.000*u.ns, 0.213*u.ns, -0.861*u.ns, -0.396*u.ns, 0.231*u.ns, -0.286*u.ns, + -1.118*u.ns, 0.000*u.ns, -0.005*u.ns, 0.569*u.ns, 0.823*u.ns, -0.506*u.ns, 1.050*u.ns, + -0.455*u.ns, 0.000*u.ns, -1.324*u.ns, 0.337*u.ns, -2.054*u.ns, -1.458*u.ns, -0.756*u.ns, + -0.790*u.ns, 0.000*u.ns, -1.279*u.ns, -1.031*u.ns, -0.108*u.ns, -1.363*u.ns, 1.033*u.ns] + constants['t_11676']=[ 0.000*u.ns, 0.000*u.ns, -0.374*u.ns, -0.527*u.ns, 0.144*u.ns, -0.406*u.ns, -0.090*u.ns, + -1.324*u.ns, 0.000*u.ns, 0.618*u.ns, -0.929*u.ns, -0.019*u.ns, 0.613*u.ns, 0.056*u.ns, + -1.098*u.ns, 0.000*u.ns, -0.045*u.ns, 0.584*u.ns, 0.915*u.ns, -0.543*u.ns, 1.006*u.ns, + -0.433*u.ns, 0.000*u.ns, -1.322*u.ns, 0.322*u.ns, -2.106*u.ns, -1.526*u.ns, -0.797*u.ns, + -0.816*u.ns, 0.000*u.ns, -1.254*u.ns, -1.008*u.ns, 0.299*u.ns, -1.388*u.ns, 1.048*u.ns] # scifi_time_aligment_consts = { 2022: ['t_0', 't_4361','t_5117'], 2023: ['t_0', 't_5478', 't_6208', 't_6443', 't_6677'], 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'], - 2025: ['t_0'] + 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676'] } scifi_time_tags = scifi_time_aligment_consts[year] for c in scifi_time_tags: From 141cccb562375de19b4fed6417141644305ccbf4 Mon Sep 17 00:00:00 2001 From: siilieva Date: Sun, 31 Aug 2025 13:23:07 +0200 Subject: [PATCH 21/99] Add MuFi spatial alignment for 2025 --- shipLHC/modifyGeoFileDict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index 600712c623..de1b3e8899 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -38,7 +38,7 @@ def modifyDicts(year=2024): 2022: [ 0.11,-0.04, 0.00, 0.10, 0.26, 0.24, 0.31, 0.34, 0.43, 1.13, 0.53, 1.31, 0.61, 1.35, 1.39], 2023: [ 0.15, 0.03, 0.00,-0.01, 0.09,-0.01, 0.06, 0.06, 0.49, 0.86, 0.20, 0.98, 0.24, 1.01, 1.11], 2024: [-0.06, 0.04, 0.65,-0.15,-0.12,-0.26,-0.29,-0.36, 0.00, 0.35,-0.37, 0.38,-0.41, 0.30, 0.35], - 2025: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + 2025: [-0.04, 0.05, 0.66,-0.16,-0.15,-0.31,-0.34,-0.45,-0.10, 0.39,-0.48, 0.43,-0.91,-0.36,-2.02] } mufi_spatial_aligment_keys = ['Veto1ShiftY','Veto2ShiftY','Veto3ShiftX', 'US1ShiftY','US2ShiftY','US3ShiftY','US4ShiftY','US5ShiftY', From 6cc3902229e6b321b23aae883a924a7f2c1f0847 Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 1 Sep 2025 10:30:55 +0200 Subject: [PATCH 22/99] Add DS time alignment for target runs 251 to 254 --- shipLHC/modifyGeoFileDict.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index de1b3e8899..9de759168f 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -71,13 +71,18 @@ def modifyDicts(year=2024): constants['t_9692'] = [-6.58,-6.67,-6.76,-6.95,-6.47,-6.53,-6.07,-6.20,-6.22,-6.39,-6.01,-6.18,-8.23,-8.26,-8.43,-8.65,-8.19,-8.27,-8.20,-8.35] constants['t_9882'] = [-6.22,-6.13,-6.37,-6.25,-6.02,-6.18,-5.65,-5.58,-5.76,-5.79,-5.53,-5.75,-7.81,-7.69,-8.02,-8.07,-7.74,-7.85,-7.81,-7.98] constants['t_10012']= [-6.70,-6.84,-6.73,-6.86,-6.50,-6.64,-6.04,-6.19,-6.21,-6.41,-6.05,-6.24,-8.23,-8.38,-8.40,-8.64,-8.22,-8.38,-8.31,-8.52] + # 2025 + constants['t_10423']= [-6.61,-6.69,-6.68,-6.77,-6.44,-6.58,-5.98,-6.06,-6.11,-6.29,-5.98,-6.16,-8.16,-8.17,-8.24,-8.43,-8.13,-8.22,-8.26,-8.41] + constants['t_11158']= [-6.60,-6.68,-6.79,-6.86,-6.51,-6.60,-6.08,-6.13,-6.20,-6.32,-6.04,-6.20,-8.19,-8.26,-8.34,-8.49,-8.19,-8.29,-8.31,-8.44] + constants['t_11576']= [-6.41,-6.48,-6.72,-6.78,-6.43,-6.53,-5.94,-6.06,-6.19,-6.36,-6.00,-6.17,-8.17,-8.20,-8.30,-8.49,-8.08,-8.16,-8.11,-8.21] + constants['t_11676']= [-6.42,-6.47,-6.74,-6.79,-6.43,-6.53,-5.95,-6.06,-6.21,-6.35,-6.01,-6.17,-8.16,-8.22,-8.32,-8.50,-8.09,-8.17,-8.13,-8.22] ds_time_aligment_consts = { 2022: {"t_0":0.000, "t_4361":0.082, "t_5117":0.085}, 2023: {"t_0":0.000, "t_5478":0.082, "t_6208":0.086, "t_6443":0.082, "t_6677":0.084}, 2024: {"t_0":0.000, "t_7649":0.081, "t_8318":0.082, "t_8583":0.081, "t_8942":0.080, "t_9156":0.083, "t_9286":0.082, "t_9379":0.083, "t_9462":0.083, "t_9613":0.082, "t_9692":0.078, "t_9882":0.084, "t_10012":0.085}, - 2025: {"t_0": 0.000} + 2025: {"t_0": 0.000, "t_10423":0.083, "t_11158":0.082, "t_11576":0.079, "t_11676":0.080} } slopes_dict = ds_time_aligment_consts[year] #time delay corrections first order, only for DS at the moment From d52621392805902d66412168ad148ea297e759e3 Mon Sep 17 00:00:00 2001 From: Pedro1102 <134416660+Pedro1102@users.noreply.github.com> Date: Mon, 8 Sep 2025 04:33:06 -0400 Subject: [PATCH 23/99] Add MCevent builder FairTask (#277) Adding MC Event Builder Task and its run manager. Noise filter parameters are stored in a namespace, together with other used constants. Beware: One has to free memory of the TClonesArray to avoid point/track leaking to following event chunks. Some specifics of the task are: - An event's header is copied to all its chunked events. - The mother track is only copied in the first event, unless it has MC points in the following ones. - To keep track of track indices, one has to create MCTrack array of the same size for each event chunk. Originally each such array is filled with dummy tracks using the ShipMCTrack standard ctor (PdgCode = 0). Only tracks having MC points in a given chunk are overwritten with their true values. --------- Co-authored-by: Oliver Lantwin Co-authored-by: siilieva --- shipLHC/MuFilterPoint.h | 10 +- shipLHC/run_MCEventBuilder.py | 62 +++ sndFairTasks/CMakeLists.txt | 1 + sndFairTasks/DigiTaskSND.cxx | 4 + sndFairTasks/MCEventBuilder.cxx | 627 +++++++++++++++++++++++++++++ sndFairTasks/MCEventBuilder.h | 73 ++++ sndFairTasks/sndFairTasksLinkDef.h | 1 + 7 files changed, 773 insertions(+), 5 deletions(-) create mode 100644 shipLHC/run_MCEventBuilder.py create mode 100644 sndFairTasks/MCEventBuilder.cxx create mode 100644 sndFairTasks/MCEventBuilder.h diff --git a/shipLHC/MuFilterPoint.h b/shipLHC/MuFilterPoint.h index c6784c78f3..df33249e72 100644 --- a/shipLHC/MuFilterPoint.h +++ b/shipLHC/MuFilterPoint.h @@ -38,17 +38,17 @@ class MuFilterPoint : public FairMCPoint Int_t PdgCode() const {return fPdgCode;} + + /** Copy constructor **/ + MuFilterPoint(const MuFilterPoint& point); + + MuFilterPoint operator=(const MuFilterPoint& point); private: Int_t fPdgCode; - /** Copy constructor **/ - - MuFilterPoint(const MuFilterPoint& point); - MuFilterPoint operator=(const MuFilterPoint& point); - ClassDef(MuFilterPoint,1) }; diff --git a/shipLHC/run_MCEventBuilder.py b/shipLHC/run_MCEventBuilder.py new file mode 100644 index 0000000000..ea58b0e9dd --- /dev/null +++ b/shipLHC/run_MCEventBuilder.py @@ -0,0 +1,62 @@ +import os +import ROOT +from ROOT import TObjString +import time +from argparse import ArgumentParser +import SndlhcGeo +import shipunit as u + +# ------------ Argument parser ---------------- +parser = ArgumentParser() +parser.add_argument("-f", "--inputFile", help="Input file") +parser.add_argument("-g", "--geoFile", help="geo file") +parser.add_argument("-o", "--outputFile", help="Output file") +parser.add_argument("-i", "--firstEvent", type=int, default=0, help="First event to process") +parser.add_argument("-n", "--nEvents", type=int, default=0, help="Number of events to process (0 = all)") +parser.add_argument("--saveFirst25nsOnly", action="store_true", help="Only store the first 25-ns chunk of each event") +options = parser.parse_args() + +# ------------ Geo setup ---------------- +geo = SndlhcGeo.GeoInterface(options.geoFile) +lsOfGlobals = ROOT.gROOT.GetListOfGlobals() +lsOfGlobals.Add(geo.modules['Scifi']) +scifiDet = lsOfGlobals.FindObject('Scifi') +scifiDet.SetConfPar("Scifi/signalSpeed", 15*u.cm/u.nanosecond) +lsOfGlobals.Add(geo.modules['MuFilter']) + + +#-----------Executioner-------------- +start = time.time() +inRootTFile = ROOT.TFile(options.inputFile) +print(f"Input file: {options.inputFile}") + +# Use FairRoot framework to arrange the workflow +# A FairRun is a wrapper of a collection of tasks +run = ROOT.FairRunAna() + +# Input/output manager +ioman = ROOT.FairRootManager.Instance() +source = ROOT.FairFileSource(inRootTFile) +run.SetSource(source) +outFile = ROOT.TMemFile('dummy','CREATE') #IGNORE +sink = ROOT.FairRootFileSink(outFile) +run.SetSink(sink) +ioman.InitSink() +run.SetEventHeaderPersistence(False) + +#Avoiding some error messages +xrdb = ROOT.FairRuntimeDb.instance() +xrdb.getContainer("FairBaseParSet").setStatic() +xrdb.getContainer("FairGeoParSet").setStatic() + +# Add tasks +eventBuilder = ROOT.MCEventBuilder(options.outputFile, options.saveFirst25nsOnly) +run.AddTask(eventBuilder) + +# Initialize and run +run.Init() +run.Run(options.firstEvent, options.firstEvent+options.nEvents) + +end = time.time() +elapsed = end - start +print(f"Elapsed time: {elapsed:.2f} seconds") diff --git a/sndFairTasks/CMakeLists.txt b/sndFairTasks/CMakeLists.txt index dc730ad889..ef4d3f8bd8 100644 --- a/sndFairTasks/CMakeLists.txt +++ b/sndFairTasks/CMakeLists.txt @@ -29,6 +29,7 @@ set(SRCS DigiTaskSND.cxx ConvRawData.cxx boardMappingParser.cxx +MCEventBuilder.cxx ) Set(HEADERS) diff --git a/sndFairTasks/DigiTaskSND.cxx b/sndFairTasks/DigiTaskSND.cxx index 26d9bf5b82..61c8b0ee3c 100644 --- a/sndFairTasks/DigiTaskSND.cxx +++ b/sndFairTasks/DigiTaskSND.cxx @@ -61,6 +61,10 @@ InitStatus DigiTaskSND::Init() // .. with a safety net for trailing dots mischief if ( fMCEventHeader == nullptr ) { fMCEventHeader = static_cast(gROOT->FindObjectAny("MCEventHeader.")); + } + if ( fMCEventHeader == nullptr ) { + ioman->GetInTree()->SetBranchAddress("MCEventHeader.", &fMCEventHeader); + LOG(INFO) << "MCEventHeader. branch is found"; } // Get input MC points fScifiPointArray = static_cast(ioman->GetObject("ScifiPoint")); diff --git a/sndFairTasks/MCEventBuilder.cxx b/sndFairTasks/MCEventBuilder.cxx new file mode 100644 index 0000000000..42202b984c --- /dev/null +++ b/sndFairTasks/MCEventBuilder.cxx @@ -0,0 +1,627 @@ +#include "MCEventBuilder.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FairMCEventHeader.h" +#include "FairFileHeader.h" +#include "MuFilter.h" +#include "Scifi.h" +#include "FairLink.h" +#include "FairRunSim.h" +#include "FairRunAna.h" +#include "FairRootManager.h" +#include "MuFilterPoint.h" +#include "ScifiPoint.h" +#include "ShipMCTrack.h" +#include "ShipUnit.h" + +class vetoPoint; +class EmulsionDetPoint; + +namespace { + int n_clockcycles = 4; + + float t_electronics = 0.0; //if known + float timeWindow = n_clockcycles * (1 /(ShipUnit::snd_freq)) + t_electronics; + + MuFilter* MuFilterDet = nullptr; + float DsPropSpeed = 0.0; + float VandUpPropSpeed = 0.0; + + Scifi* ScifiDet = nullptr; + float ScifisignalSpeed = 0.0; + std::map>> siPMFibres; + + //Scifi Fast & Advanced Noise Filter Params + int min_scifi_boards = 3; + int min_us_boards = 5; + int min_ds_boards = 2; + + int min_veto_hits = 5; + int min_scifi_hits = 10; + int min_ds_hits =2; + + int min_veto_planes = 1; + int min_scifi_planes = 4; + int min_us_planes = 2; + int min_ds_planes = 2; +} + +MCEventBuilder::MCEventBuilder(const std::string& outputFileName, + bool saveFirst25nsOnly) + : FairTask("MCEventBuilder"), + fOutputFileName(outputFileName), + fSaveFirst25nsOnly(saveFirst25nsOnly), + fOutFile(nullptr), + fOutTree(nullptr), + fInMufiArray(nullptr), + fInSciFiArray(nullptr), + fInMCTrackArray(nullptr), + fInHeader(nullptr), + fOutMufiArray(nullptr), + fOutSciFiArray(nullptr), + fOutMCTrackArray(nullptr), + fOutHeader(nullptr) +{} + +MCEventBuilder::~MCEventBuilder() {} + +InitStatus MCEventBuilder::Init() { + LOG(INFO) << "Initializing MCEventBuilder"; + + FairRootManager* ioman = FairRootManager::Instance(); + if (!ioman) { + LOG(FATAL) << "MCEventBuilder::Init: RootManager not instantiated!"; + } + + // —---------- INPUT BRANCHES —----------- + // Only standard ROOT way of reading branches works for the header, otherwise nullptr + ioman->GetInTree()->SetBranchAddress("MCEventHeader.",&fInHeader); + fInMufiArray = static_cast(ioman->GetObject("MuFilterPoint")); + fInSciFiArray = static_cast(ioman->GetObject("ScifiPoint")); + fInMCTrackArray = static_cast(ioman->GetObject("MCTrack")); + + if (!fInMufiArray && !fInSciFiArray) { + LOG(ERROR) << "No Scifi and no MuFilter MC points array!"; + return kERROR; + } + + // —---------- OUTPUT FILE & TREE —---------- + fOutFile = new TFile(fOutputFileName.c_str(), "RECREATE"); + fOutTree = new TTree("cbmsim", "RebuiltEvents"); + fOutTree->SetTitle("/cbmroot_0"); + + fOutHeader = new FairMCEventHeader(); + fOutMufiArray = new TClonesArray("MuFilterPoint"); + fOutSciFiArray = new TClonesArray("ScifiPoint"); + fOutMCTrackArray = new TClonesArray("ShipMCTrack"); + fOutMufiArray->SetName("MuFilterPoint"); + fOutSciFiArray->SetName("ScifiPoint"); + fOutMCTrackArray->SetName("ShipMCTrack"); + + fOutTree->Branch("MuFilterPoint", &fOutMufiArray, 32000, 1); + fOutTree->Branch("ScifiPoint", &fOutSciFiArray, 32000, 1); + fOutTree->Branch("MCTrack", &fOutMCTrackArray, 32000, 1); + fOutTree->Branch("MCEventHeader.", &fOutHeader); + + // --------------Global Variables-------------------- + //Muon Filter + MuFilterDet = dynamic_cast(gROOT->GetListOfGlobals()->FindObject("MuFilter")); + if (!MuFilterDet) { + LOG(ERROR) << "MuFilter detector not found in gROOT"; + return kERROR; + } + DsPropSpeed = MuFilterDet->GetConfParF("MuFilter/DsPropSpeed"); + VandUpPropSpeed = MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed"); + + //Scifi + ScifiDet = dynamic_cast(gROOT->GetListOfGlobals()->FindObject("Scifi")); + if (!ScifiDet) { + LOG(ERROR) << "Scifi detector not found in gROOT"; + return kERROR; + } + ScifisignalSpeed = ScifiDet->GetConfParF("Scifi/signalSpeed"); + ScifiDet->SiPMmapping(); + siPMFibres = ScifiDet->GetFibresMap(); + + LOG(INFO) << "MCEventBuilder initialized successfully."; + return kSUCCESS; +} + +void MCEventBuilder::Exec(Option_t*) { + ProcessEvent(); +} + +std::vector MCEventBuilder::OrderedIds(const std::vector& times, + double firstTime) const { + size_t n = times.size(); + std::vector bins(n); + for (size_t i = 0; i < n; ++i) { + bins[i] = static_cast((times[i] - firstTime) / timeWindow); + } + + std::vector ids(n, 0); + for (size_t i = 1; i < n; ++i) { + if ((bins[i] - bins[i - 1]) < 1) { + ids[i] = ids[i - 1]; + } else { + ids[i] = ids[i - 1] + 1; + } + } + return ids; +} + +void MCEventBuilder::ProcessEvent() { + + fOutHeader->SetRunID(fInHeader->GetRunID()); + fOutHeader->SetEventID(fInHeader->GetEventID()); + fOutHeader->SetVertex(fInHeader->GetX(), fInHeader->GetY(), fInHeader->GetZ()); + fOutHeader->SetTime(fInHeader->GetT()); + fOutHeader->SetB(fInHeader->GetB()); + fOutHeader->SetNPrim(fInHeader->GetNPrim()); + fOutHeader->MarkSet(fInHeader->IsSet()); + fOutHeader->SetRotX(fInHeader->GetRotX()); + fOutHeader->SetRotY(fInHeader->GetRotY()); + fOutHeader->SetRotZ(fInHeader->GetRotZ()); + + //---------------------------Muon filter------------------------------------- + std::vector muFilterPoints; + std::vector muArrivalTimes; + std::vector muTrackIDs; + + for (auto* p : ROOT::RRangeCast(*fInMufiArray)) { + muFilterPoints.push_back(p); + muTrackIDs.push_back(p->GetTrackID()); + + int detID = p->GetDetectorID(); + + float propspeed; + if (floor(detID / 10000) == 3) + propspeed = DsPropSpeed; + else + propspeed = VandUpPropSpeed; + + TVector3 vLeft,vRight; + TVector3 impact(p->GetX(), p->GetY(), p->GetZ()); + MuFilterDet->GetPosition(detID, vLeft, vRight); + TVector3 vTop = vLeft; //Used for vertical bars + + //Vertical bars with only 1 readout at the top + if ( (floor(detID/10000)==3&&detID%1000>59) || + (floor(detID/10000)==1&&int(detID/1000)%10==2) ) { + double arrivalTime = p->GetTime() + (vTop - impact).Mag() / propspeed; + muArrivalTimes.push_back(arrivalTime); + } + //Horizontal + else{ + double tLeft = p->GetTime() + (vLeft - impact).Mag() / propspeed; + double tRight = p->GetTime() + (vRight - impact).Mag() / propspeed; + double arrivalTime = std::min(tLeft, tRight); + muArrivalTimes.push_back(arrivalTime); + } + } + + std::vector idxM(muArrivalTimes.size()); + std::iota(idxM.begin(), idxM.end(), 0); + std::sort(idxM.begin(), idxM.end(), [&](size_t a, size_t b) { + return muArrivalTimes[a] < muArrivalTimes[b]; + }); + + std::vector sortedMuPoints; + std::vector sortedMuArrivalTimes; + std::vector sortedMuTrackIDs; + + sortedMuPoints.reserve(muFilterPoints.size()); + sortedMuArrivalTimes.reserve(muArrivalTimes.size()); + sortedMuTrackIDs.reserve(muTrackIDs.size()); + + for (auto i : idxM) { + sortedMuPoints.push_back(muFilterPoints[i]); + sortedMuArrivalTimes.push_back(muArrivalTimes[i]); + sortedMuTrackIDs.push_back(muTrackIDs[i]); + } + + //---------------------------Scifi------------------------------------- + std::vector scifiPoints; + std::vector scifiArrivalTimes; + std::vector scifiTrackIDs; + + float signalSpeed = ScifisignalSpeed; + + for (auto* p : ROOT::RRangeCast(*fInSciFiArray)) { + scifiPoints.push_back(p); + scifiTrackIDs.push_back(p->GetTrackID()); + + TVector3 impact(p->GetX(), p->GetY(), p->GetZ()); + int point_detID = p->GetDetectorID(); + int localFiberID = (point_detID)%100000; + int a_sipmChan = static_cast(siPMFibres[localFiberID].begin()->first); + int detID_geo = int(point_detID/100000)*100000+a_sipmChan; + + TVector3 a, b; + ScifiDet->GetSiPMPosition(detID_geo, a, b); + bool verticalHit = int(detID_geo / 100000) % 10 == 1; + double distance; + if (verticalHit) { + distance = (b - impact).Mag(); + } else { + distance = (impact - a).Mag(); + } + double arrivalTime = p->GetTime() + distance / signalSpeed; + scifiArrivalTimes.push_back(arrivalTime); + } + + std::vector idxS(scifiArrivalTimes.size()); + std::iota(idxS.begin(), idxS.end(), 0); + std::sort(idxS.begin(), idxS.end(), [&](size_t a, size_t b) { + return scifiArrivalTimes[a] < scifiArrivalTimes[b]; + }); + + std::vector sortedScifiPoints; + std::vector sortedScifiArrivalTimes; + std::vector sortedScifiTrackIDs; + + sortedScifiPoints.reserve(scifiPoints.size()); + sortedScifiArrivalTimes.reserve(scifiArrivalTimes.size()); + sortedScifiTrackIDs.reserve(scifiTrackIDs.size()); + + for (auto i : idxS) { + sortedScifiPoints.push_back(scifiPoints[i]); + sortedScifiArrivalTimes.push_back(scifiArrivalTimes[i]); + sortedScifiTrackIDs.push_back(scifiTrackIDs[i]); + } + + //----------------------------------Tracks------------------------------------- + std::vector mcTrackClones; + for (auto* t : ROOT::RRangeCast(*fInMCTrackArray)) { + mcTrackClones.push_back(t); + } + + //---------Finding the earliest time between Scifi and MuFilter----------------- + double tMufi = sortedMuArrivalTimes.empty() ? -1 : sortedMuArrivalTimes.front(); + double tScifi = sortedScifiArrivalTimes.empty() ? -1 : sortedScifiArrivalTimes.front(); + bool hasMCPoints = (tMufi >= 0 || tScifi >= 0); + double firstT = hasMCPoints ? (tMufi < 0 ? tScifi : (tScifi < 0 ? tMufi : std::min(tMufi, tScifi))): 0; + + if (!hasMCPoints) { + fOutMufiArray->Delete(); + fOutSciFiArray->Delete(); + fOutMCTrackArray->Delete(); + fOutTree->Fill(); + return; + } + + //------------------Preparations before chunking the data--------------------- + auto idsMufi = OrderedIds(sortedMuArrivalTimes, firstT); + auto idsScifi = OrderedIds(sortedScifiArrivalTimes, firstT); + + bool FirstEvent = true; + + std::vector used; + int i_mufi = 0, i_scifi = 0, sliceMufi = 0, sliceScifi = 0; + + while (i_mufi < (int)sortedMuArrivalTimes.size() || i_scifi < (int)sortedScifiArrivalTimes.size()) { + // To avoid re-copy of points or tracks from chunk N to following chunks, one needs to free the memory + // e.g. chunk N has points with track ids(indices) T-10, T-5, T, chunk N+1 holds points with track id T+1. + // If memory is not de-allocated, all tracks of smaller index than T+1 will be also written to chunk N+1. + // This will be incorrect since there are no points of tracks with id < T in the N+1 chunck. + fOutMufiArray->Delete(); + fOutSciFiArray->Delete(); + fOutMCTrackArray->Delete(); + + std::vector muSlicePoints; + std::vector scifiSlicePoints; + fOutMCTrackArray->ExpandCreate(mcTrackClones.size()); + + //Adding the mother track to the first event + if (sliceMufi==0 && sliceScifi==0){ + ShipMCTrack* newTrack = new ((*fOutMCTrackArray)[0]) ShipMCTrack(*mcTrackClones[0]); + } + + //MUON FILTER POINTS CHUNKING + while (i_mufi < (int)sortedMuArrivalTimes.size() && idsMufi[i_mufi] == sliceMufi) { + MuFilterPoint* origMu = sortedMuPoints[i_mufi]; + muSlicePoints.push_back(origMu); + Int_t trackID = origMu->GetTrackID(); + Int_t detID = origMu->GetDetectorID(); + TVector3 pos; origMu->Position(pos); + TVector3 mom; origMu->Momentum(mom); + Double_t time = origMu->GetTime(); + Double_t length = origMu->GetLength(); + Double_t eLoss = origMu->GetEnergyLoss(); + Int_t pdgCode = origMu->PdgCode(); + + new ((*fOutMufiArray)[fOutMufiArray->GetEntriesFast()]) + MuFilterPoint(trackID, detID, pos, mom, time, length, eLoss, pdgCode); + + int tid = sortedMuTrackIDs[i_mufi++]; + //MC tracks having ID=-2 are below the pre-set energy threshold (default is 100MeV) and cannot be linked to their corresponding MC point. Such tracks are not saved to the chunked events. + if (tid != -2) { + ShipMCTrack* newTrack = new ((*fOutMCTrackArray)[tid]) ShipMCTrack(*mcTrackClones[tid]); + } + } + ++sliceMufi; + + //SCIFI POINTS CHUNKING + while (i_scifi < (int)sortedScifiArrivalTimes.size() && idsScifi[i_scifi] == sliceScifi) { + ScifiPoint* origSci = sortedScifiPoints[i_scifi]; + scifiSlicePoints.push_back(origSci); + Int_t trackID = origSci->GetTrackID(); + Int_t detID = origSci->GetDetectorID(); + TVector3 pos; origSci->Position(pos); + TVector3 mom; origSci->Momentum(mom); + Double_t time = origSci->GetTime(); + Double_t length = origSci->GetLength(); + Double_t eLoss = origSci->GetEnergyLoss(); + Int_t pdgCode = origSci->PdgCode(); + + new ((*fOutSciFiArray)[fOutSciFiArray->GetEntriesFast()]) + ScifiPoint(trackID, detID, pos, mom, time, length, eLoss, pdgCode); + + int tid = sortedScifiTrackIDs[i_scifi++]; + //MC tracks having ID=-2 are below the pre-set energy threshold (default is 100MeV) and cannot be linked to their corresponding MC point. Such tracks are not saved to the chunked events. + if (tid != -2) { + ShipMCTrack* newTrack = new ((*fOutMCTrackArray)[tid]) ShipMCTrack(*mcTrackClones[tid]); + } + } + ++sliceScifi; + + //Noise Filters + if (!(FastNoiseFilterScifi_Hits(fOutSciFiArray, siPMFibres) || FastNoiseFilterMu_Hits(fOutMufiArray))) { + fOutMufiArray->Delete(); + fOutSciFiArray->Delete(); + fOutMCTrackArray->Delete(); + FirstEvent = false; + continue; + } + else if(!(FastNoiseFilterScifi_Boards(fOutSciFiArray, siPMFibres) || FastNoiseFilterMu_Boards(fOutMufiArray))){ + fOutMufiArray->Delete(); + fOutSciFiArray->Delete(); + fOutMCTrackArray->Delete(); + FirstEvent = false; + continue; + } + + if (!(AdvancedNoiseFilterScifi(fOutSciFiArray, siPMFibres) || AdvancedNoiseFilterMu(fOutMufiArray))){ + fOutMufiArray->Delete(); + fOutSciFiArray->Delete(); + fOutMCTrackArray->Delete(); + FirstEvent = false; + continue; + } + + //--------Save only first 25ns chunks mode------- + if (fSaveFirst25nsOnly) { + if (FirstEvent) { + fOutTree->Fill(); + FirstEvent = false; + } + } + //-----Save all chunks mode------- + else { + fOutTree->Fill(); + } + } +} + +//-----------------------Mu Fast Noise Filtering------------------------------- +bool MCEventBuilder::FastNoiseFilterMu_Hits(TClonesArray* muArray) { + std::set veto, ds; + for (int i = 0; i < muArray->GetEntriesFast(); ++i) { + auto* p = static_cast(muArray->At(i)); + int detID = p->GetDetectorID(); + int system = detID / 10000; + if (system == 1) { + veto.insert(detID); + if (veto.size() >= min_veto_hits) { + return true; + } + } + else if (system == 3) { + ds.insert(detID); + if (ds.size() >= min_ds_hits) { + return true; + } + } + } + return false; +} + +bool MCEventBuilder::FastNoiseFilterMu_Boards(TClonesArray* muArray) { + //The digits used here to assign IDs to systems are set to match the DAQ board IDs from the 2022 board_mapping.json file + std::set us, ds; + for (int i = 0; i < muArray->GetEntriesFast(); ++i) { + auto* p = static_cast(muArray->At(i)); + int detID = p->GetDetectorID(); + int system = detID / 10000; + int TypeofPlane = (detID / 1000) % 10; + if (system == 2) { + if (TypeofPlane == 1 || TypeofPlane == 2) { + us.insert(7); + } else if (TypeofPlane == 3 || TypeofPlane == 4) { + us.insert(60); + } else if (TypeofPlane == 5) { + us.insert(52); + } + if (us.size() >= min_us_boards) { + return true; + } + } else if (system == 3) { + if (TypeofPlane == 1) { + ds.insert(41); + } else if (TypeofPlane == 2) { + ds.insert(35); + } else if (TypeofPlane == 3 || TypeofPlane == 4) { + ds.insert(55); + } + if (ds.size() >= min_ds_boards) { + return true; + } + } + } + return false; +} + +//-----------------------Mu Advanced Noise Filtering------------------------------- +bool MCEventBuilder::AdvancedNoiseFilterMu(TClonesArray* muArray) { + std::set veto_planes, us_planes, ds_planes; + for (int i = 0; i < muArray->GetEntriesFast(); ++i) { + auto* p = static_cast(muArray->At(i)); + int detID = p->GetDetectorID(); + int system = detID / 10000; + if (system == 1) { + veto_planes.insert(detID / 1000); + if (veto_planes.size() >= min_veto_planes) { + return true; + } + } else if (system == 2) { + us_planes.insert(detID / 1000); + if (us_planes.size() >= min_us_planes) { + return true; + } + } else if (system == 3) { + ds_planes.insert(detID / 1000); + if (ds_planes.size() >= min_ds_planes) { + return true; + } + } + } + return false; +} + + +//-----------------------Scifi Fast Noise Filtering------------------------------- +bool MCEventBuilder::FastNoiseFilterScifi_Hits( + TClonesArray* scifiArray, + const std::map>>& siPMFibresMap) + { + std::set detIDs; + for (int i = 0; i < scifiArray->GetEntriesFast(); ++i) { + auto* p = static_cast(scifiArray->At(i)); + int point_detID = p->GetDetectorID(); + int locFibreID = point_detID % 100000; + + for (const auto& sipmChan : siPMFibresMap.at(locFibreID)) { + int channel = sipmChan.first; + int detID_geo = (point_detID / 100000) * 100000 + channel; + detIDs.insert(detID_geo); + + if (detIDs.size() >= min_scifi_hits){ + return true; + } + } + } + return false; + } + +bool MCEventBuilder::FastNoiseFilterScifi_Boards( + TClonesArray* scifiArray, + const std::map>>& siPMFibresMap) + { + //A DAQ board reads one Scifi mat and we use the first three digits of the detID_geo, representing the unique combination of StationPlaneMat, to count boards with fired channels. + std::set dividedDetIds; + + for (int i = 0; i < scifiArray->GetEntriesFast(); ++i) { + auto* p = static_cast(scifiArray->At(i)); + int point_detID = p->GetDetectorID(); + int locFibreID = point_detID % 100000; + + for (const auto& sipmChan : siPMFibresMap.at(locFibreID)) { + int channel = sipmChan.first; + int detID_geo = (point_detID / 100000) * 100000 + channel; + dividedDetIds.insert(detID_geo / 10000); + + if (dividedDetIds.size() >= min_scifi_boards){ + return true; + } + } + } + return false; + } + +//-----------------------Scifi Advanced Noise Filtering------------------------------- +bool MCEventBuilder::AdvancedNoiseFilterScifi( + TClonesArray* scifiArray, + const std::map>>& siPMFibresMap) + { + std::set UniquePlanes; + for (int i = 0; i < scifiArray->GetEntriesFast(); ++i) { + auto* p = static_cast(scifiArray->At(i)); + int point_detID = p->GetDetectorID(); + int locFibreID = point_detID % 100000; + + for (const auto& sipmChan : siPMFibresMap.at(locFibreID)) { + int channel = sipmChan.first; + int detID_geo = (point_detID / 100000) * 100000 + channel; + UniquePlanes.insert(detID_geo / 100000); + + if (UniquePlanes.size() >= min_scifi_planes) { + return true; + } + } + } + return false; + } + +void MCEventBuilder::FinishTask() { + // Make the output file structure compatible with FairTasks to facilitate + // runing the digitization task. + fOutFile->cd(); + TFolder* folder = new TFolder("cbmroot", "Main Folder"); + TFolder* fol_Stack = folder->AddFolder("Stack", "Stack Folder"); + auto mcTrackArr = new TClonesArray("ShipMCTrack"); + mcTrackArr->SetName("MCTrack"); // avoid default plural names + fol_Stack->Add(mcTrackArr); + TFolder* fol_veto = folder->AddFolder("veto", "veto Folder"); + auto vetoArr = new TClonesArray("vetoPoint"); + vetoArr->SetName("vetoPoint"); + fol_veto->Add(vetoArr); + TFolder* fol_EmulsionDet = folder->AddFolder("EmulsionDet", "EmulsionDetector Folder"); + auto emuArr = new TClonesArray("EmulsionDetPoint"); + emuArr->SetName("EmulsionDetPoint"); + fol_EmulsionDet->Add(emuArr); + TFolder* fol_Scifi = folder->AddFolder("Scifi", "Scifi Folder"); + auto scifiArr = new TClonesArray("ScifiPoint"); + scifiArr->SetName("ScifiPoint"); + fol_Scifi->Add(scifiArr); + TFolder* fol_MuFilter = folder->AddFolder("MuFilter", "MuFilter Folder"); + auto mufArr = new TClonesArray("MuFilterPoint"); + mufArr->SetName("MuFilterPoint"); + fol_MuFilter->Add(mufArr); + TFolder* fol_Event = folder->AddFolder("Event", "Event Folder"); + folder->Write(); + + fOutFile->cd(); + TList* branch_list = new TList(); + branch_list->SetName("BranchList"); + branch_list->Add(new TObjString("MCTrack")); + branch_list->Add(new TObjString("vetoPoint")); + branch_list->Add(new TObjString("EmulsionDetPoint")); + branch_list->Add(new TObjString("ScifiPoint")); + branch_list->Add(new TObjString("MuFilterPoint")); + branch_list->Add(new TObjString("MCEventHeader.")); + fOutFile->WriteObject(branch_list, "BranchList"); + auto* timebased_branch_list = new TList(); + timebased_branch_list->SetName("TimeBasedBranchList"); + fOutFile->WriteObject(timebased_branch_list, "TimeBasedBranchList"); + fOutFile->cd(); + auto* fileHeader = new FairFileHeader(); + fileHeader->SetTitle("FileHeader"); + fileHeader->Write("FileHeader"); + fOutFile->cd(); + if (fOutTree) { + fOutTree->Write(); + } + LOG(INFO) << "Writing and closing output file: " << fOutputFileName; +} diff --git a/sndFairTasks/MCEventBuilder.h b/sndFairTasks/MCEventBuilder.h new file mode 100644 index 0000000000..0d4069db5f --- /dev/null +++ b/sndFairTasks/MCEventBuilder.h @@ -0,0 +1,73 @@ +#ifndef MCEVENTBUILDER_H +#define MCEVENTBUILDER_H +#include +#include "FairTask.h" +#include +#include +#include +#include "FairMCPoint.h" +#include +#include + +class TFile; +class TTree; +class MuFilterPoint; +class ScifiPoint; +class ShipMCTrack; +class FairMCEventHeader; + +class MCEventBuilder : public FairTask { +public: + MCEventBuilder(const std::string& outputFileName, bool saveOnlyFirst25); + ~MCEventBuilder(); + + virtual InitStatus Init(); + virtual void Exec(Option_t* opt); + virtual void FinishTask(); + +private: + //Function I need later for ordering the mc points + std::vector OrderedIds(const std::vector& times, double firstTime) const; + + //Function that does all the processing + void ProcessEvent(); + + // Fast filter functions + bool FastNoiseFilterMu_Hits(TClonesArray* muArray); + bool FastNoiseFilterMu_Boards(TClonesArray* muArray); + + bool FastNoiseFilterScifi_Hits( + TClonesArray* scifiArray, + const std::map>>& siPMFibres); + bool FastNoiseFilterScifi_Boards( + TClonesArray* scifiArray, + const std::map>>& siPMFibres); + + //Advanced Noise Filter + bool AdvancedNoiseFilterScifi( + TClonesArray* scifiArray, + const std::map>>& siPMFibres); + + bool AdvancedNoiseFilterMu(TClonesArray* muArray); + + //Input + bool fSaveFirst25nsOnly; + FairMCEventHeader* fInHeader; + TClonesArray* fInMufiArray; + TClonesArray* fInSciFiArray; + TClonesArray* fInMCTrackArray; + + //Output + std::string fOutputFileName; + TFile* fOutFile; + TTree* fOutTree; + FairMCEventHeader* fOutHeader; + TClonesArray* fOutMufiArray; + TClonesArray* fOutSciFiArray; + TClonesArray* fOutMCTrackArray; + + + ClassDef(MCEventBuilder, 1) +}; + +#endif // MCEVENTBUILDER_H diff --git a/sndFairTasks/sndFairTasksLinkDef.h b/sndFairTasks/sndFairTasksLinkDef.h index 59168d5240..7ea592291b 100755 --- a/sndFairTasks/sndFairTasksLinkDef.h +++ b/sndFairTasks/sndFairTasksLinkDef.h @@ -6,6 +6,7 @@ #pragma link C++ class DigiTaskSND; #pragma link C++ class ConvRawData; +#pragma link C++ class MCEventBuilder; #endif From 24d197c57deff5e8c70d30b7e57fb6a670ed9a6f Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 8 Sep 2025 10:48:53 +0200 Subject: [PATCH 24/99] Update CHANGELOG before new release with MCEventBuilder --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5862420e5..94a82ae6a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,12 @@ We start with the first sndsw release: v1.0.0+2025-07-updateScifi. Shall there be a strong will/need, one can go back and create and fill in the logs for previous stacks. +## v1.2.0+2025-09-MCEventBuilder + +### Added + +- MCEventBuilder Fair Task + ## v1.1.0+2025-08-BolognaTools ### Added From c008e6fd2490e4e74079b3f7f0185c4708542782 Mon Sep 17 00:00:00 2001 From: alessia-orlando Date: Fri, 26 Sep 2025 11:26:38 +0200 Subject: [PATCH 25/99] Unifiy data/MC channel index determination in Scifi/US planes (#282) Move from TClonesArray to std::vector in the plane class functions! --- analysis/tools/sndPlaneTools.cxx | 60 +++++++++++++++----------------- analysis/tools/sndScifiPlane.cxx | 28 ++++++++------- analysis/tools/sndScifiPlane.h | 6 ++-- analysis/tools/sndUSPlane.cxx | 11 ++---- analysis/tools/sndUSPlane.h | 4 +-- 5 files changed, 51 insertions(+), 58 deletions(-) diff --git a/analysis/tools/sndPlaneTools.cxx b/analysis/tools/sndPlaneTools.cxx index 24342bdb7a..342c887d9a 100644 --- a/analysis/tools/sndPlaneTools.cxx +++ b/analysis/tools/sndPlaneTools.cxx @@ -15,51 +15,47 @@ std::vector snd::analysis_tools::FillScifi(cons { std::vector scifi_planes; + int n_sf_hits{sf_hits->GetEntries()}; - int begin{0}; - int count{0}; + const int max_station = configuration.scifi_n_stations; + std::vector> stations_hits(max_station); - int n_sf_hits{sf_hits->GetEntries()}; + for (int i{0}; i < n_sf_hits; ++i) { + auto hit = static_cast(sf_hits->At(i)); + int station_id = hit->GetStation()-1; - for (int st{1}; st <= configuration.scifi_n_stations; ++st) - { - begin = count; - while (count < n_sf_hits && - st == static_cast(sf_hits->At(count))->GetStation()) - { - ++count; - } - scifi_planes.emplace_back(snd::analysis_tools::ScifiPlane(sf_hits, configuration, scifi_geometry, begin, count, st)); + if (station_id > -1 && station_id < max_station) { + stations_hits[station_id].push_back(hit); + } + else throw std::runtime_error{"Invalid SciFi station"}; + } + for (int st{0}; st < max_station; ++st) { + scifi_planes.emplace_back(snd::analysis_tools::ScifiPlane(stations_hits[st], configuration, scifi_geometry, st+1)); } return scifi_planes; } + std::vector snd::analysis_tools::FillUS(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry) { std::vector us_planes; - - int begin{0}; - int count{0}; - int n_mufi_hits{mufi_hits->GetEntries()}; - // skip veto/beam monitor - while (count < n_mufi_hits && - static_cast(mufi_hits->At(count))->GetSystem() != 2) - { - ++count; + + const int n_station = configuration.us_n_stations; + std::vector> plane_hits(n_station); + + for (int i{0}; i < n_mufi_hits; ++i) { + auto hit = static_cast(mufi_hits->At(i)); + if (hit->GetSystem()!=2) continue; + int station_id = hit->GetPlane(); + if (station_id > -1 && station_id < n_station) { + plane_hits[station_id].push_back(hit); + } + else throw std::runtime_error{"Invalid US plane"}; } - // plane count starts from 0 - for (int pl{0}; pl < configuration.us_n_stations; ++pl) - { - begin = count; - while (count < n_mufi_hits && - pl == static_cast(mufi_hits->At(count))->GetPlane() && - static_cast(mufi_hits->At(count))->GetSystem() == 2) // stop before DS - { - ++count; - } - us_planes.emplace_back(snd::analysis_tools::USPlane(mufi_hits, configuration, mufilter_geometry, begin, count, pl + 1)); + for (int st{0}; st < n_station; ++st) { + us_planes.emplace_back(snd::analysis_tools::USPlane(plane_hits[st], configuration, mufilter_geometry, st+1)); } return us_planes; } \ No newline at end of file diff --git a/analysis/tools/sndScifiPlane.cxx b/analysis/tools/sndScifiPlane.cxx index ef3cf80420..d0ad4ed788 100644 --- a/analysis/tools/sndScifiPlane.cxx +++ b/analysis/tools/sndScifiPlane.cxx @@ -6,24 +6,18 @@ #include #include -#include "TClonesArray.h" #include "TVector3.h" #include "Scifi.h" #include "sndScifiHit.h" +#include "ShipUnit.h" -snd::analysis_tools::ScifiPlane::ScifiPlane(TClonesArray *snd_hits, snd::Configuration configuration, Scifi *scifi_geometry, int index_begin, int index_end, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""), std::nan("")), station_(station) +snd::analysis_tools::ScifiPlane::ScifiPlane(std::vector snd_hits, Configuration configuration, Scifi *scifi_geometry, int station) : station_(station), configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""), std::nan("")) { - if (index_begin > index_end) + for ( auto snd_hit : snd_hits) { - throw std::runtime_error{"Begin index > end index"}; - } - - for (int i{index_begin}; i < index_end; ++i) - { - auto snd_hit = static_cast(snd_hits->At(i)); ScifiHit hit; - hit.channel_index = 512 * snd_hit->GetMat() + 64 * snd_hit->GetTofpetID(0) + 63 - snd_hit->Getchannel(0); - hit.timestamp = snd_hit->GetTime(0); + hit.channel_index = 512 * snd_hit->GetMat() + 128 * snd_hit->GetSiPM() + snd_hit->GetSiPMChan(); + hit.timestamp = (scifi_geometry->GetCorrectedTime(snd_hit->GetDetectorID(), snd_hit->GetTime(0)*ShipUnit::snd_TDC2ns, 0) / ShipUnit::snd_TDC2ns); // timestamp is in clock cycles hit.qdc = snd_hit->GetSignal(0); hit.is_x = snd_hit->isVertical(); @@ -62,16 +56,26 @@ bool snd::analysis_tools::ScifiPlane::IsShower() const throw std::runtime_error{"min_hits > window_width"}; } + if (configuration_.scifi_shower_window_width > configuration_.scifi_n_channels_per_plane) + { + throw std::runtime_error{"window_width > n_channels_per_plane"}; + } + xy_pair> is_hit; is_hit.x.resize(configuration_.scifi_n_channels_per_plane, 0); is_hit.y.resize(configuration_.scifi_n_channels_per_plane, 0); for (auto &hit : hits_) { + if (hit.channel_index < 0 || + hit.channel_index >= configuration_.scifi_n_channels_per_plane) + { + throw std::out_of_range{"hit.channel_index out of range"}; + } (hit.is_x ? is_hit.x : is_hit.y)[hit.channel_index] = 1; } - auto density = [&](std::vector &hit_arr) + auto density = [&](const std::vector &hit_arr) { int count{0}; diff --git a/analysis/tools/sndScifiPlane.h b/analysis/tools/sndScifiPlane.h index 884406ab47..58ef2a615b 100644 --- a/analysis/tools/sndScifiPlane.h +++ b/analysis/tools/sndScifiPlane.h @@ -4,9 +4,9 @@ #include #include "TVector3.h" -#include "TClonesArray.h" #include "Scifi.h" #include "sndConfiguration.h" +#include "sndScifiHit.h" namespace snd { namespace analysis_tools { @@ -24,7 +24,7 @@ namespace snd { struct ScifiHit { double qdc{}; - double timestamp{}; + double timestamp{}; // timestamp is in clock cycles double x{}; double y{}; double z{}; @@ -32,7 +32,7 @@ namespace snd { bool is_x{}; }; - ScifiPlane(TClonesArray *snd_hits, Configuration configuration, Scifi *scifi_geometry, int index_begin, int index_end, int station); + ScifiPlane(std::vector snd_hits, Configuration configuration, Scifi *scifi_geometry, int station); const int GetStation() const { return station_; }; const std::vector GetHits() const { return hits_; }; diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index 0492ed5235..8f2f9f9f04 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -5,21 +5,14 @@ #include #include -#include "TClonesArray.h" #include "TVector3.h" #include "MuFilter.h" #include "MuFilterHit.h" -snd::analysis_tools::USPlane::USPlane(TClonesArray *snd_hits, snd::Configuration configuration, MuFilter *muon_filter_geometry, int index_begin, int index_end, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) +snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, Configuration configuration, MuFilter *muon_filter_geometry, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) { - if (index_begin > index_end) + for ( auto mu_hit : snd_hits) { - throw std::runtime_error{"Begin index > end index"}; - } - for (int j{index_begin}; j < index_end; ++j) - { - auto mu_hit = static_cast(snd_hits->At(j)); - for (int i{0}; i < 16; ++i) { if (mu_hit->isMasked(i) || mu_hit->GetSignal(i) < -990.) continue; diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h index 1ed3e26a8a..61ca65ceb3 100644 --- a/analysis/tools/sndUSPlane.h +++ b/analysis/tools/sndUSPlane.h @@ -4,8 +4,8 @@ #include #include "TVector3.h" -#include "TClonesArray.h" #include "MuFilter.h" +#include "MuFilterHit.h" #include "sndConfiguration.h" namespace snd { @@ -45,7 +45,7 @@ namespace snd { bool is_right; }; - USPlane(TClonesArray *snd_hits, Configuration configuration, MuFilter *muon_filter_geometry, int index_begin, int index_end, int station); + USPlane(std::vector snd_hits, Configuration configuration, MuFilter *muon_filter_geometry, int station); const sl_pair GetNHits() const; const int GetStation() const { return station_; }; From afb2578ef05d8bc270a621fe545767ee4b42e709 Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 29 Sep 2025 12:07:02 +0200 Subject: [PATCH 26/99] Alignment of target 255 --- shipLHC/modifyGeoFileDict.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index 9de759168f..ddc6a3cdba 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -76,13 +76,15 @@ def modifyDicts(year=2024): constants['t_11158']= [-6.60,-6.68,-6.79,-6.86,-6.51,-6.60,-6.08,-6.13,-6.20,-6.32,-6.04,-6.20,-8.19,-8.26,-8.34,-8.49,-8.19,-8.29,-8.31,-8.44] constants['t_11576']= [-6.41,-6.48,-6.72,-6.78,-6.43,-6.53,-5.94,-6.06,-6.19,-6.36,-6.00,-6.17,-8.17,-8.20,-8.30,-8.49,-8.08,-8.16,-8.11,-8.21] constants['t_11676']= [-6.42,-6.47,-6.74,-6.79,-6.43,-6.53,-5.95,-6.06,-6.21,-6.35,-6.01,-6.17,-8.16,-8.22,-8.32,-8.50,-8.09,-8.17,-8.13,-8.22] + constants['t_11795']= [-6.43,-6.45,-6.71,-6.79,-6.43,-6.52,-5.99,-6.09,-6.15,-6.32,-5.98,-6.13,-8.17,-8.25,-8.29,-8.48,-8.08,-8.14,-8.16,-8.24] ds_time_aligment_consts = { 2022: {"t_0":0.000, "t_4361":0.082, "t_5117":0.085}, 2023: {"t_0":0.000, "t_5478":0.082, "t_6208":0.086, "t_6443":0.082, "t_6677":0.084}, 2024: {"t_0":0.000, "t_7649":0.081, "t_8318":0.082, "t_8583":0.081, "t_8942":0.080, "t_9156":0.083, "t_9286":0.082, "t_9379":0.083, "t_9462":0.083, "t_9613":0.082, "t_9692":0.078, "t_9882":0.084, "t_10012":0.085}, - 2025: {"t_0": 0.000, "t_10423":0.083, "t_11158":0.082, "t_11576":0.079, "t_11676":0.080} + 2025: {"t_0": 0.000, "t_10423":0.083, "t_11158":0.082, "t_11576":0.079, "t_11676":0.080, + "t_11795": 0.079} } slopes_dict = ds_time_aligment_consts[year] #time delay corrections first order, only for DS at the moment @@ -212,13 +214,18 @@ def modifyDicts(year=2024): -1.098*u.ns, 0.000*u.ns, -0.045*u.ns, 0.584*u.ns, 0.915*u.ns, -0.543*u.ns, 1.006*u.ns, -0.433*u.ns, 0.000*u.ns, -1.322*u.ns, 0.322*u.ns, -2.106*u.ns, -1.526*u.ns, -0.797*u.ns, -0.816*u.ns, 0.000*u.ns, -1.254*u.ns, -1.008*u.ns, 0.299*u.ns, -1.388*u.ns, 1.048*u.ns] + constants['t_11795']=[ 0.000*u.ns, 0.000*u.ns, -0.359*u.ns, -0.524*u.ns, 0.124*u.ns, -0.417*u.ns, -0.084*u.ns, + -1.327*u.ns, 0.000*u.ns, 0.628*u.ns, -0.722*u.ns, -0.167*u.ns, 0.608*u.ns, 0.140*u.ns, + -1.078*u.ns, 0.000*u.ns, -0.014*u.ns, 0.569*u.ns, 0.930*u.ns, -0.501*u.ns, 1.000*u.ns, + -0.417*u.ns, 0.000*u.ns, -1.360*u.ns, 0.336*u.ns, -2.079*u.ns, -1.514*u.ns, -0.775*u.ns, + -0.736*u.ns, 0.000*u.ns, -1.317*u.ns, -1.076*u.ns, 0.296*u.ns, -1.402*u.ns, 0.989*u.ns] # scifi_time_aligment_consts = { 2022: ['t_0', 't_4361','t_5117'], 2023: ['t_0', 't_5478', 't_6208', 't_6443', 't_6677'], 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'], - 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676'] + 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795'] } scifi_time_tags = scifi_time_aligment_consts[year] for c in scifi_time_tags: @@ -618,13 +625,29 @@ def modifyDicts(year=2024): 0.7200*u.mrad, 0.2000*u.mrad, 0.0000*u.mrad, -1.0244*u.mrad, -0.5787*u.mrad, 0.0171*u.mrad, 0.9128*u.mrad, 0.4085*u.mrad, 0.0171*u.mrad] + alignment['t_11795']=[ #2025 emulsion run 23 run_255 + 327.80*u.um, 296.15*u.um, 357.10*u.um, + 49.60*u.um, 102.00*u.um, 25.95*u.um, + -81.10*u.um, -82.15*u.um, -114.40*u.um, + 21.55*u.um, 90.50*u.um, -4.55*u.um, + -45.00*u.um, -36.60*u.um, -83.76*u.um, + 65.00*u.um, 2.20*u.um, 96.00*u.um, + -126.80*u.um, -67.85*u.um, -94.90*u.um, + 29.05*u.um, -20.00*u.um, -29.05*u.um, + 400.60*u.um, 430.85*u.um, 331.80*u.um, + -146.00*u.um, 119.80*u.um, 17.65*u.um, + -0.24*u.mrad, -0.05*u.mrad, -0.30*u.mrad, + -0.06*u.mrad, -0.18*u.mrad, -0.15*u.mrad, + 0.48*u.mrad, 0.00*u.mrad, 0.00*u.mrad, + 0.32*u.mrad, -0.28*u.mrad, 0.23*u.mrad, + -0.20*u.mrad, 0.40*u.mrad, 1.32*u.mrad] scifi_spatial_aligment_consts = { 2022: ['t_0', 't_4361','t_4575','t_4855','t_5172'], 2023: ['t_0', 't_5431', 't_6443', 't_6677'], 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'], - 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676'] + 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795'] } scifi_spatial_tags = scifi_spatial_aligment_consts[year] for c in scifi_spatial_tags: From c71d7af52d89e1b306c5e4fe4233f8396ba8e7a7 Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 29 Sep 2025 12:10:51 +0200 Subject: [PATCH 27/99] Break loop over event chuncks after first one if option is saveFirst25nsOnly Spotted by @olantwin --- sndFairTasks/MCEventBuilder.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/sndFairTasks/MCEventBuilder.cxx b/sndFairTasks/MCEventBuilder.cxx index 42202b984c..f8d31cdbc7 100644 --- a/sndFairTasks/MCEventBuilder.cxx +++ b/sndFairTasks/MCEventBuilder.cxx @@ -406,6 +406,7 @@ void MCEventBuilder::ProcessEvent() { if (FirstEvent) { fOutTree->Fill(); FirstEvent = false; + break; } } //-----Save all chunks mode------- From c4b436803546ec26d0be5925e2787adea64b4cbf Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 29 Sep 2025 14:48:10 +0200 Subject: [PATCH 28/99] MCEventBuilder: save the outgoing neutrino in NC events to 1st chunk This gives a hande to tag NC nu events in analysis. --- sndFairTasks/MCEventBuilder.cxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sndFairTasks/MCEventBuilder.cxx b/sndFairTasks/MCEventBuilder.cxx index f8d31cdbc7..b60403c2e0 100644 --- a/sndFairTasks/MCEventBuilder.cxx +++ b/sndFairTasks/MCEventBuilder.cxx @@ -327,6 +327,11 @@ void MCEventBuilder::ProcessEvent() { //Adding the mother track to the first event if (sliceMufi==0 && sliceScifi==0){ ShipMCTrack* newTrack = new ((*fOutMCTrackArray)[0]) ShipMCTrack(*mcTrackClones[0]); + // In NC neutrino events, the outgoing neutrino is also saved in the first chunk, + // otherwise it will be lost. This is to facilitate NC event tagging in analysis. + if (mcTrackClones.size()>1 && std::set({12,14,16}).count(fabs(mcTrackClones[1]->GetPdgCode()))){ + ShipMCTrack* newTrack = new ((*fOutMCTrackArray)[1]) ShipMCTrack(*mcTrackClones[1]); + } } //MUON FILTER POINTS CHUNKING From 582ff5616126d335abaaba358a8e13b8c9b76f7c Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 29 Sep 2025 14:54:35 +0200 Subject: [PATCH 29/99] Update CHANGELOG before patch release v1.2.1 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94a82ae6a0..8d939e61bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,22 @@ We start with the first sndsw release: v1.0.0+2025-07-updateScifi. Shall there be a strong will/need, one can go back and create and fill in the logs for previous stacks. +## v1.2.1+2025-09 + +### Added + +- alignment of target 255 + +### Changed + +- optimize the MCEventBuider: + - break loop over chunks after first one if using option saveFirst25nsOnly + - in NC neutrino events, the outgoing neutrino is now saved in the first chunk + +### Fixed + +- fix in SciFi and US plane classes to allow channel indexing in MC and data alike + ## v1.2.0+2025-09-MCEventBuilder ### Added From 413b711df8262346d0e4b101162ae4c0c8357a60 Mon Sep 17 00:00:00 2001 From: siilieva Date: Thu, 9 Oct 2025 14:12:55 +0200 Subject: [PATCH 30/99] Workaround for re-running digitization on old MC Before July 2022 the Scifi signal speed was not determined, thus missing from the geofiles. At that time it was not a problem for digitization cause light propagation in the fibers was not simulated. The latter effect was added in Nov 2023. Ever since, if one re-digitizes old MC, it is essential to have that parameter set. --- shipLHC/run_digiSND.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shipLHC/run_digiSND.py b/shipLHC/run_digiSND.py index 91efc660e3..776714dd45 100644 --- a/shipLHC/run_digiSND.py +++ b/shipLHC/run_digiSND.py @@ -84,6 +84,9 @@ def mem_monitor(): scifiDet.SetConfPar("Scifi/nphe_max",options.ss) # saturation scifiDet.SetConfPar("Scifi/timeResol",150.*u.picosecond) # time resolution in ps scifiDet.SetConfPar("MuFilter/timeResol",150.*u.picosecond) # time resolution in ps, first guess +# in MC productions generated before July 2022 Scifi signal speed is missing from the geofile +if scifiDet.GetConfParF("Scifi/signalSpeed")==0: + scifiDet.SetConfPar("Scifi/signalSpeed", 15*u.cm/u.nanosecond) # Fair digitization task From 1dd8ed48cfdd3ee233507dd2bc2799ac2c0c9296 Mon Sep 17 00:00:00 2001 From: Filippo Mei <67705874+FelixofRivia@users.noreply.github.com> Date: Tue, 14 Oct 2025 06:28:30 +0200 Subject: [PATCH 31/99] Shower tools (#283) * Add tools for shower starting station and direction --- analysis/tools/AnalysisToolsLinkDef.h | 6 ++ analysis/tools/CMakeLists.txt | 3 +- analysis/tools/sndScifiPlane.cxx | 25 ++--- analysis/tools/sndScifiPlane.h | 16 +-- analysis/tools/sndShowerTools.cxx | 149 ++++++++++++++++++++++++++ analysis/tools/sndShowerTools.h | 29 +++++ analysis/tools/sndUSPlane.cxx | 4 +- analysis/tools/sndUSPlane.h | 11 +- 8 files changed, 215 insertions(+), 28 deletions(-) create mode 100644 analysis/tools/sndShowerTools.cxx create mode 100644 analysis/tools/sndShowerTools.h diff --git a/analysis/tools/AnalysisToolsLinkDef.h b/analysis/tools/AnalysisToolsLinkDef.h index 1bb1d1b467..f1bc6c6936 100644 --- a/analysis/tools/AnalysisToolsLinkDef.h +++ b/analysis/tools/AnalysisToolsLinkDef.h @@ -39,5 +39,11 @@ #pragma link C++ function snd::analysis_tools::GetGeometry(const std::string &); #pragma link C++ function snd::analysis_tools::FillScifi(const snd::Configuration &, TClonesArray *, Scifi *); #pragma link C++ function snd::analysis_tools::FillUS(const snd::Configuration &, TClonesArray *, MuFilter *); +#pragma link C++ function snd::analysis_tools::GetScifiShowerStart(const std::vector &); +#pragma link C++ function snd::analysis_tools::GetUSShowerEnd(const std::vector &); +#pragma link C++ function snd::analysis_tools::GetShowerInterceptAndDirection(const snd::Configuration &, const std::vector &, const std::vector &); +#pragma link C++ function snd::analysis_tools::GetShoweringPlanes(const std::vector &, const std::vector &); #endif diff --git a/analysis/tools/CMakeLists.txt b/analysis/tools/CMakeLists.txt index 4b76e02a20..e0910586d2 100644 --- a/analysis/tools/CMakeLists.txt +++ b/analysis/tools/CMakeLists.txt @@ -17,6 +17,7 @@ sndConfiguration.cxx sndScifiPlane.cxx sndUSPlane.cxx sndPlaneTools.cxx +sndShowerTools.cxx ) set(LINK_DIRECTORIES @@ -33,4 +34,4 @@ Set(DEPENDENCIES shipLHC) GENERATE_LIBRARY() -target_link_libraries(snd_analysis_tools -lROOTTPython) \ No newline at end of file +target_link_libraries(snd_analysis_tools -lROOTTPython -lMatrix -lMathCore -lCore) \ No newline at end of file diff --git a/analysis/tools/sndScifiPlane.cxx b/analysis/tools/sndScifiPlane.cxx index d0ad4ed788..3e8cb3a6de 100644 --- a/analysis/tools/sndScifiPlane.cxx +++ b/analysis/tools/sndScifiPlane.cxx @@ -10,8 +10,9 @@ #include "Scifi.h" #include "sndScifiHit.h" #include "ShipUnit.h" +#include "Math/Point3D.h" -snd::analysis_tools::ScifiPlane::ScifiPlane(std::vector snd_hits, Configuration configuration, Scifi *scifi_geometry, int station) : station_(station), configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""), std::nan("")) +snd::analysis_tools::ScifiPlane::ScifiPlane(std::vector snd_hits, Configuration configuration, Scifi *scifi_geometry, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""), std::nan("")), station_(station) { for ( auto snd_hit : snd_hits) { @@ -44,12 +45,12 @@ const snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPl xy_pair counts{0, 0}; counts.x = std::count_if(hits_.begin(), hits_.end(), [](auto &hit) { return hit.is_x; }); - counts.y = hits_.size() - counts.x; + counts.y = static_cast(hits_.size()) - counts.x; return counts; } -bool snd::analysis_tools::ScifiPlane::IsShower() const +bool snd::analysis_tools::ScifiPlane::HasShower() const { if (configuration_.scifi_min_hits_in_window > configuration_.scifi_shower_window_width) { @@ -102,7 +103,7 @@ bool snd::analysis_tools::ScifiPlane::IsShower() const return density(is_hit.x) && density(is_hit.y); } -const TVector3 snd::analysis_tools::ScifiPlane::GetCluster(int max_gap) const +const ROOT::Math::XYZPoint snd::analysis_tools::ScifiPlane::GetCluster(int max_gap) const { std::vector pos_x(configuration_.scifi_n_channels_per_plane, std::nan("")); std::vector pos_y(configuration_.scifi_n_channels_per_plane, std::nan("")); @@ -124,7 +125,7 @@ const TVector3 snd::analysis_tools::ScifiPlane::GetCluster(int max_gap) const auto largest_cluster = [&](const std::vector &positions) { - int n = positions.size(); + int n = static_cast(positions.size()); int best_start = -1, best_end = -1, best_size = 0; int start = -1, gap_count = 0, size = 0; @@ -191,9 +192,9 @@ const TVector3 snd::analysis_tools::ScifiPlane::GetCluster(int max_gap) const double cluster_x = largest_cluster(pos_x); double cluster_y = largest_cluster(pos_y); if (!(std::isnan(cluster_x) || std::isnan(cluster_y))) { - return TVector3(cluster_x, cluster_y, hits_[0].z); + return ROOT::Math::XYZPoint(cluster_x, cluster_y, hits_[0].z); } - return TVector3(std::nan(""), std::nan(""), std::nan("")); + return ROOT::Math::XYZPoint(std::nan(""), std::nan(""), std::nan("")); } void snd::analysis_tools::ScifiPlane::TimeFilter(double min_timestamp, double max_timestamp) @@ -204,7 +205,7 @@ void snd::analysis_tools::ScifiPlane::TimeFilter(double min_timestamp, double ma hits_.end()); } -snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPlane::GetPointQdc(const TVector3 &point, double radius) const +snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPlane::GetPointQdc(const ROOT::Math::XYZPoint &point, double radius) const { xy_pair qdc{0.0, 0.0}; for (const auto &hit : hits_) { @@ -231,9 +232,9 @@ void snd::analysis_tools::ScifiPlane::FindCentroid() cleaned_hits.end()); int counts_x = std::count_if(cleaned_hits.begin(), cleaned_hits.end(), [](auto &hit) { return hit.is_x; }); - int counts_y = cleaned_hits.size()-counts_x; + int counts_y = static_cast(cleaned_hits.size())-counts_x; if (counts_x < configuration_.scifi_min_n_hits_for_centroid && counts_y < configuration_.scifi_min_n_hits_for_centroid ) { - centroid_.SetXYZ(std::nan(""), std::nan(""), std::nan("")); + centroid_ = ROOT::Math::XYZPoint(std::nan(""), std::nan(""), std::nan("")); return; } @@ -255,8 +256,8 @@ void snd::analysis_tools::ScifiPlane::FindCentroid() centroid_.SetZ(centroid_.Z() + hit.z * hit.qdc); } } - centroid_.SetXYZ((tot_qdc_x > 0) ? centroid_.X() / tot_qdc_x : std::nan(""), (tot_qdc_y > 0) ? centroid_.Y() / tot_qdc_y : std::nan(""), (tot_qdc_x+tot_qdc_y > 0) ? centroid_.Z() / (tot_qdc_x+tot_qdc_y) : std::nan("")); - centroid_error_.SetXYZ(configuration_.scifi_centroid_error_x, configuration_.scifi_centroid_error_y, configuration_.scifi_centroid_error_z); + centroid_ = ROOT::Math::XYZPoint((tot_qdc_x > 0) ? centroid_.X() / tot_qdc_x : std::nan(""), (tot_qdc_y > 0) ? centroid_.Y() / tot_qdc_y : std::nan(""), (tot_qdc_x+tot_qdc_y > 0) ? centroid_.Z() / (tot_qdc_x+tot_qdc_y) : std::nan("")); + centroid_error_ = ROOT::Math::XYZPoint(configuration_.scifi_centroid_error_x, configuration_.scifi_centroid_error_y, configuration_.scifi_centroid_error_z); } const snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPlane::GetTotQdc(bool only_positive) const diff --git a/analysis/tools/sndScifiPlane.h b/analysis/tools/sndScifiPlane.h index 58ef2a615b..077eff9495 100644 --- a/analysis/tools/sndScifiPlane.h +++ b/analysis/tools/sndScifiPlane.h @@ -3,10 +3,10 @@ #include -#include "TVector3.h" #include "Scifi.h" #include "sndConfiguration.h" #include "sndScifiHit.h" +#include "Math/Point3D.h" namespace snd { namespace analysis_tools { @@ -36,25 +36,25 @@ namespace snd { const int GetStation() const { return station_; }; const std::vector GetHits() const { return hits_; }; - const TVector3 GetCentroid() const { return centroid_; }; - const TVector3 GetCentroidError() const { return centroid_error_; } + const ROOT::Math::XYZPoint GetCentroid() const { return centroid_; }; + const ROOT::Math::XYZPoint GetCentroidError() const { return centroid_error_; } const xy_pair GetTotQdc(bool only_positive = false) const; const xy_pair GetTotEnergy(bool only_positive = false) const; const xy_pair GetNHits() const; // Position of larger cluster of consecutive hits, allowing at most max_gap channels with no hit - const TVector3 GetCluster(int max_gap) const; + const ROOT::Math::XYZPoint GetCluster(int max_gap) const; // The centroid is the qdc-weighted mean of hit positions, considering only hits with positive qdc void FindCentroid(); - bool IsShower() const; + bool HasShower() const; void TimeFilter(double min_timestamp, double max_timestamp); // qdc from hits within a given point and radius (square, not circle) - xy_pair GetPointQdc(const TVector3 &point, double radius) const; + xy_pair GetPointQdc(const ROOT::Math::XYZPoint &point, double radius) const; private: std::vector hits_; Configuration configuration_; - TVector3 centroid_; - TVector3 centroid_error_; + ROOT::Math::XYZPoint centroid_; + ROOT::Math::XYZPoint centroid_error_; int station_; }; diff --git a/analysis/tools/sndShowerTools.cxx b/analysis/tools/sndShowerTools.cxx new file mode 100644 index 0000000000..1daf5c5d02 --- /dev/null +++ b/analysis/tools/sndShowerTools.cxx @@ -0,0 +1,149 @@ +#include "sndShowerTools.h" + +#include +#include +#include + +#include "sndConfiguration.h" +#include "sndScifiPlane.h" +#include "sndUSPlane.h" +#include "TMatrixD.h" +#include "TVectorD.h" +#include "TDecompChol.h" +#include "Math/Vector3D.h" +#include "Math/Point3D.h" + +int snd::analysis_tools::GetScifiShowerStart(const std::vector &scifi_planes) +{ + // Assuming planes are ordered + for (const auto &p : scifi_planes) + { + if (p.HasShower()) + { + return p.GetStation(); + } + } + return -1; +} + +int snd::analysis_tools::GetScifiShowerEnd(const std::vector &scifi_planes) +{ + // Assuming planes are ordered + for (auto p = scifi_planes.rbegin(); p != scifi_planes.rend(); p++) { + if (p->HasShower()) + { + return p->GetStation(); + } + } + return -1; +} + +int snd::analysis_tools::GetUSShowerStart(const std::vector &us_planes) +{ + // Assuming planes are ordered + for (const auto &p : us_planes) + { + if (p.HasShower()) + { + return p.GetStation(); + } + } + return -1; +} + +int snd::analysis_tools::GetUSShowerEnd(const std::vector &us_planes) +{ + // Assuming planes are ordered + for (auto p = us_planes.rbegin(); p != us_planes.rend(); p++) { + if (p->HasShower()) + { + return p->GetStation(); + } + } + return -1; +} + +std::pair snd::analysis_tools::GetShowerInterceptAndDirection(const snd::Configuration &configuration, const std::vector &scifi_planes, const std::vector &us_planes) +{ + std::vector positions_x; + std::vector positions_y; + std::vector positions_z; + std::vector err_positions_x; + std::vector err_positions_y; + std::vector err_positions_z; + + auto collect_centroids = [&](const auto &planes) { + for (const auto &p : planes) { + auto centroid = p.GetCentroid(); + auto centroid_error = p.GetCentroidError(); + // Check that centroid is valid + if (!(std::isnan(centroid.X()) || std::isnan(centroid.Y()) || std::isnan(centroid.Z()))) { + positions_x.push_back(centroid.X()); + positions_y.push_back(centroid.Y()); + positions_z.push_back(centroid.Z()); + err_positions_x.push_back(centroid_error.X()); + err_positions_y.push_back(centroid_error.Y()); + err_positions_z.push_back(centroid_error.Z()); + } + } + }; + + collect_centroids(scifi_planes); + collect_centroids(us_planes); + + if (static_cast(positions_z.size()) < configuration.centroid_min_valid_station) { + return std::make_pair(ROOT::Math::XYZPoint(std::nan(""), std::nan(""), std::nan("")), ROOT::Math::XYZVector(std::nan(""), std::nan(""), std::nan(""))); + } + + auto weighted_linear_fit = [](const std::vector &z, const std::vector &val, const std::vector &err) + { + const int n_points = z.size(); + const int n_variables = 2; // intercept + slope + + // Wrap existing memory into TVectorD + TVectorD v_z; + v_z.Use(n_points, const_cast(z.data())); + TVectorD v_y; + v_y.Use(n_points, const_cast(val.data())); + TVectorD v_e; + v_e.Use(n_points, const_cast(err.data())); + + TMatrixD matrix(n_points, n_variables); + TMatrixDColumn(matrix,0) = 1.0; + TMatrixDColumn(matrix,1) = v_z; + + // Solve normal equations (weighted least squares) + TVectorD coeffs = NormalEqn(matrix, v_y, v_e); + + return std::make_pair(coeffs[0], coeffs[1]); // (intercept, slope) + }; + + auto [intercept_x, slope_x] = weighted_linear_fit(positions_z, positions_x, err_positions_x); + auto [intercept_y, slope_y] = weighted_linear_fit(positions_z, positions_y, err_positions_y); + + ROOT::Math::XYZPoint shower_intercept(intercept_x, intercept_y, 0.0); + ROOT::Math::XYZVector shower_direction(slope_x, slope_y, 1.0); + + return std::make_pair(shower_intercept, shower_direction.Unit()); +} + +std::pair, std::vector> snd::analysis_tools::GetShoweringPlanes(const std::vector &scifi_planes, const std::vector &us_planes) +{ + std::vector sh_scifi_planes; + std::vector sh_us_planes; + + // Filter showering Scifi planes + std::copy_if(scifi_planes.begin(), scifi_planes.end(), std::back_inserter(sh_scifi_planes), [](const snd::analysis_tools::ScifiPlane& p) { return p.HasShower(); }); + // Filter showering US planes + std::copy_if(us_planes.begin(), us_planes.end(), std::back_inserter(sh_us_planes), [](const snd::analysis_tools::USPlane& p) { return p.HasShower(); }); + + for (auto &p: sh_scifi_planes) { + p.FindCentroid(); + } + for (auto &p: sh_us_planes) { + p.FindCentroid(); + } + + return std::make_pair(sh_scifi_planes, sh_us_planes); +} + diff --git a/analysis/tools/sndShowerTools.h b/analysis/tools/sndShowerTools.h new file mode 100644 index 0000000000..bf1e64e34a --- /dev/null +++ b/analysis/tools/sndShowerTools.h @@ -0,0 +1,29 @@ +#ifndef SND_SHOWERTOOLS_H +#define SND_SHOWERTOOLS_H + +#include + +#include "sndConfiguration.h" +#include "sndScifiPlane.h" +#include "sndUSPlane.h" +#include "Math/Vector3D.h" +#include "Math/Point3D.h" + +namespace snd { + namespace analysis_tools { + // Returns first SciFi station with shower. If no shower is found in SciFi, returns -1 + int GetScifiShowerStart(const std::vector &scifi_planes); + // Returns last SciFi station with shower. If no shower is found in SciFi, returns -1 + int GetScifiShowerEnd(const std::vector &scifi_planes); + // Returns first US station with shower. If no shower is found in US, returns -1 + int GetUSShowerStart(const std::vector &us_planes); + // Returns last US station with shower. If no shower is found in US, returns -1 + int GetUSShowerEnd(const std::vector &us_planes); + // Returns a 3D reference point and the normalized direction of the shower fitting centroids in SciFi and US + std::pair GetShowerInterceptAndDirection(const Configuration &configuration, const std::vector &scifi_planes, const std::vector &us_planes); + // Filters out non showering planes + std::pair, std::vector> GetShoweringPlanes(const std::vector &scifi_planes, const std::vector &us_planes); + } +} + +#endif \ No newline at end of file diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index 8f2f9f9f04..219357a28c 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -38,7 +38,7 @@ snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, Config void snd::analysis_tools::USPlane::FindCentroid() { // min number of hit in the plane to attempt to find a centroid - if (hits_.size() < configuration_.us_min_n_hits_for_centroid) + if (static_cast(hits_.size()) < configuration_.us_min_n_hits_for_centroid) { // std::cout<<"Not enough hits in US plane " << station_ <<" to find centroid\n"; return; @@ -165,7 +165,7 @@ const snd::analysis_tools::USPlane::sl_pair snd::analysis_tools::USPlane::G sl_pair counts{0, 0}; counts.large = std::count_if(hits_.begin(), hits_.end(), [](auto &hit) { return hit.is_large; }); - counts.small = hits_.size() - counts.large; + counts.small = static_cast(hits_.size()) - counts.large; return counts; } \ No newline at end of file diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h index 61ca65ceb3..0b93d02717 100644 --- a/analysis/tools/sndUSPlane.h +++ b/analysis/tools/sndUSPlane.h @@ -3,7 +3,7 @@ #include -#include "TVector3.h" +#include "Math/Point3D.h" #include "MuFilter.h" #include "MuFilterHit.h" #include "sndConfiguration.h" @@ -56,18 +56,19 @@ namespace snd { const rl_pair GetBarQdc(int bar_to_compute) const; const sl_pair GetBarNHits(int bar_to_compute) const; const std::vector GetHits() const { return hits_; }; + double HasShower() const { return static_cast(hits_.size()) >= configuration_.us_min_n_hits_for_centroid; }; // The centroid is the qdc-weighted mean of hit positions, considering only hits with positive qdc void FindCentroid(); - const TVector3 GetCentroid() const { return centroid_; }; - const TVector3 GetCentroidError() const { return centroid_error_; }; + ROOT::Math::XYZPoint GetCentroid() const { return centroid_; }; + ROOT::Math::XYZPoint GetCentroidError() const { return centroid_error_; }; void TimeFilter(double min_timestamp, double max_timestamp); private: std::vector hits_; Configuration configuration_; - TVector3 centroid_; - TVector3 centroid_error_; + ROOT::Math::XYZPoint centroid_; + ROOT::Math::XYZPoint centroid_error_; int station_; }; } From 95781f472d27755b4d2d06187e25782b20d69caf Mon Sep 17 00:00:00 2001 From: Cristovao Vilela Date: Wed, 15 Oct 2025 10:40:17 +0100 Subject: [PATCH 32/99] Make MCEventBuilder arguments required --- shipLHC/run_MCEventBuilder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shipLHC/run_MCEventBuilder.py b/shipLHC/run_MCEventBuilder.py index ea58b0e9dd..3c7d67efaf 100644 --- a/shipLHC/run_MCEventBuilder.py +++ b/shipLHC/run_MCEventBuilder.py @@ -8,9 +8,9 @@ # ------------ Argument parser ---------------- parser = ArgumentParser() -parser.add_argument("-f", "--inputFile", help="Input file") -parser.add_argument("-g", "--geoFile", help="geo file") -parser.add_argument("-o", "--outputFile", help="Output file") +parser.add_argument("-f", "--inputFile", help="Input file", required=True) +parser.add_argument("-g", "--geoFile", help="geo file", required=True) +parser.add_argument("-o", "--outputFile", help="Output file", required=True) parser.add_argument("-i", "--firstEvent", type=int, default=0, help="First event to process") parser.add_argument("-n", "--nEvents", type=int, default=0, help="Number of events to process (0 = all)") parser.add_argument("--saveFirst25nsOnly", action="store_true", help="Only store the first 25-ns chunk of each event") From 34bad6a6e7a1f0ec27e27e3b24f01cb06429306e Mon Sep 17 00:00:00 2001 From: Cristovao Vilela Date: Tue, 28 Oct 2025 02:04:42 +0100 Subject: [PATCH 33/99] Change pythia8 decayer configuration method. --- gconfig/DecayConfigPy8.C | 95 ++++++++++++++++++++++++---------------- shipLHC/run_simSND.py | 3 ++ 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/gconfig/DecayConfigPy8.C b/gconfig/DecayConfigPy8.C index 3eba51e693..634b506b72 100644 --- a/gconfig/DecayConfigPy8.C +++ b/gconfig/DecayConfigPy8.C @@ -1,40 +1,59 @@ -void DecayConfig() { - - // This script uses the external decayer TPythia8Decayer in place of the - // concrete Monte Carlo native decay mechanisms only for the - // specific types of decays defined below. - - // Access the external decayer singleton and initialize it - TVirtualMCDecayer* decayer = new TPythia8Decayer(); - decayer->Init(); - - // Tell the concrete monte carlo to use the external decayer. The - // external decayer will be used for: - // i)particle decays not defined in concrete monte carlo, or - //ii)particles for which the concrete monte carlo is told - // to use the external decayer for its type via: - // gMC->SetUserDecay(pdgId); - // If this is invoked, the external decayer will be used for particles - // of type pdgId even if the concrete monte carlo has a decay mode - // already defined for that particle type. - gMC->SetExternalDecayer(decayer); - // to get the rare muon decays, HOWEVER does not work with Geant4 logic - //gMC->SetUserDecay(221); // eta - //gMC->SetUserDecay(223); // omega - //gMC->SetUserDecay(113); // rho0 - //gMC->SetUserDecay(331); // eta_prime - //gMC->SetUserDecay(333); // phi - gMC->SetUserDecay(411); - gMC->SetUserDecay(-411); - gMC->SetUserDecay(421); - gMC->SetUserDecay(-421); - gMC->SetUserDecay(4122); - gMC->SetUserDecay(-4122); - gMC->SetUserDecay(431); - gMC->SetUserDecay(-431); - gMC->SetUserDecay(15); - gMC->SetUserDecay(-15); - cout<< "External decayer DecayConfigPy8 initialized"<Init(); // Currently does nothing, but leave it in case it gets implemented. + + TVirtualMC::GetMC()->SetExternalDecayer(decayer); + + // Set list of particles to decay with external decayer via geant4 command + std::string g4cmd = "/mcPhysics/setExtDecayerSelection"; + std::cout << "DecayConfigPy8 macro: setting heavy flavour decays via Pythia8" << std::endl; + // First loop through all particles in database and add heavy flavour + for (TObject * obj : *(db->ParticleList())) { + TParticlePDG * p = dynamic_cast(obj); + + if (!p) { + std::cout << "DecayConfigPy8 WARNING: got object other than TParticlePDG from particle list" << std::endl; + continue; + } + + bool isCharm = (strcmp("CharmedMeson", p->ParticleClass()) == 0) or + (strcmp("CharmedBaryon", p->ParticleClass()) == 0) or + p->Charm(); // p->Charm() doesn't work. Leave here for future compat. + bool isBeauty = (strcmp("B-Meson", p->ParticleClass()) == 0) or + (strcmp("B-Baryon", p->ParticleClass()) == 0) or + p->Beauty(); // p->Beauty() doesn't work. Leave here for future compat. + + if (isCharm or isBeauty){ + g4cmd += " "; + g4cmd += p->GetName(); + } + } + + std::cout << "DecayConfigPy8 macro: setting other decays (short-lived and tau) via Pythia8" << std::endl; + std::vector pdgs_to_decay_with_pythia8 = { + // Short-lived resonances, for rare decays. + 221, // eta + // 223, // omega CRASHES + // 113, // rho CRASHES + 331, // eta_prime + // 333, // phi CRASHES + // Tau + 15, // tau + -15 // tau bar + }; + + for (int pdg : pdgs_to_decay_with_pythia8){ + g4cmd += " "; + g4cmd += db->GetParticle(pdg)->GetName(); + } + dynamic_cast(TVirtualMC::GetMC())->ProcessGeantCommand(g4cmd.c_str()); +} +void DecayConfig() { DecayConfigPythia8(); } diff --git a/shipLHC/run_simSND.py b/shipLHC/run_simSND.py index 85d5e19778..a4e93e430e 100644 --- a/shipLHC/run_simSND.py +++ b/shipLHC/run_simSND.py @@ -197,6 +197,7 @@ def Exec(self,opt): f'({options.EVx},{options.EVy},{options.EVz})[cm × cm × cm] \n' f'with a uniform x-y spread of (Dx,Dy)=({options.Dx},{options.Dy})[cm × cm]' f' and {options.nZSlices} z slices in steps of {options.zSliceStep}[cm].') + run.SetPythiaDecayer('DecayConfigPy8.C') ROOT.FairLogger.GetLogger().SetLogScreenLevel("WARNING") # otherwise stupid printout for each event # -----muon DIS Background------------------------ if simEngine == "muonDIS": @@ -209,6 +210,7 @@ def Exec(self,opt): primGen.AddGenerator(DISgen) options.nEvents = min(options.nEvents,DISgen.GetNevents()) inactivateMuonProcesses = True # avoid unwanted hadronic events of "incoming" muon flying backward + run.SetPythiaDecayer('DecayConfigPy8.C') print('MuDIS position info input=',mu_start, mu_end) print('Generate ',options.nEvents,' with DIS input', ' first event',options.firstEvent) @@ -257,6 +259,7 @@ def Exec(self,opt): Ntuplegen.Init(inputFile,options.firstEvent) primGen.AddGenerator(Ntuplegen) options.nEvents = min(options.nEvents,Ntuplegen.GetNevents()) + run.SetPythiaDecayer('DecayConfigPy8.C') if simEngine == "MuonBack": # reading muon tracks from FLUKA From a2c2f9ae384296f46de944b91535388a625e0155 Mon Sep 17 00:00:00 2001 From: gpsndlhc Date: Wed, 15 Oct 2025 15:27:42 +0200 Subject: [PATCH 34/99] add US hit bar function: get number of bars with n hits > thr (parameter in config) --- analysis/tools/sndConfiguration.cxx | 1 + analysis/tools/sndConfiguration.h | 1 + analysis/tools/sndUSPlane.cxx | 9 +++++++++ analysis/tools/sndUSPlane.h | 2 ++ 4 files changed, 13 insertions(+) diff --git a/analysis/tools/sndConfiguration.cxx b/analysis/tools/sndConfiguration.cxx index bdd40727b8..a5e612dd81 100644 --- a/analysis/tools/sndConfiguration.cxx +++ b/analysis/tools/sndConfiguration.cxx @@ -43,6 +43,7 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter us_max_timestamp = 3.0; us_min_n_hits_for_centroid = 15; us_qdc_to_gev = 0.0151; + us_min_hit_on_bar = 5; // Ad hoc parameters not present in geometry if (option == Option::ti18_2024_2025) diff --git a/analysis/tools/sndConfiguration.h b/analysis/tools/sndConfiguration.h index 54cf363704..7918603d64 100644 --- a/analysis/tools/sndConfiguration.h +++ b/analysis/tools/sndConfiguration.h @@ -65,6 +65,7 @@ namespace snd { int us_n_channels_per_station; int us_n_sipm_per_bar; int us_min_n_hits_for_centroid; + int us_min_hit_on_bar; int centroid_min_valid_station; diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index 219357a28c..be7f287adf 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -168,4 +168,13 @@ const snd::analysis_tools::USPlane::sl_pair snd::analysis_tools::USPlane::G counts.small = static_cast(hits_.size()) - counts.large; return counts; +} + + +const int snd::analysis_tools::USPlane::GetNHitBars() const{ + int count{0}; + for (int bar{0}; bar < configuration_.us_bar_per_station; ++bar) { + if (GetBarNHits(bar).large > configuration_.us_min_hit_on_bar) count++; + } + return count; } \ No newline at end of file diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h index 0b93d02717..f5d9a52831 100644 --- a/analysis/tools/sndUSPlane.h +++ b/analysis/tools/sndUSPlane.h @@ -61,6 +61,8 @@ namespace snd { void FindCentroid(); ROOT::Math::XYZPoint GetCentroid() const { return centroid_; }; ROOT::Math::XYZPoint GetCentroidError() const { return centroid_error_; }; + const int GetNHitBars() const; + void TimeFilter(double min_timestamp, double max_timestamp); From 8b6e7d26d507f38a83bf6fc2006fe5be7884e297 Mon Sep 17 00:00:00 2001 From: gpsndlhc Date: Wed, 15 Oct 2025 15:28:17 +0200 Subject: [PATCH 35/99] fix timeref intervals --- analysis/tools/sndConfiguration.cxx | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/analysis/tools/sndConfiguration.cxx b/analysis/tools/sndConfiguration.cxx index a5e612dd81..b9ee37f0bd 100644 --- a/analysis/tools/sndConfiguration.cxx +++ b/analysis/tools/sndConfiguration.cxx @@ -37,10 +37,10 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter ds_ver_spatial_resolution_z = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarZ_ver") / std::sqrt(12); // Common parameters not present in geometry - scifi_min_timestamp = -0.5; - scifi_max_timestamp = 0.5; - us_min_timestamp = -0.5; - us_max_timestamp = 3.0; + scifi_min_timestamp = std::nan(""); + scifi_max_timestamp = std::nan(""); + us_min_timestamp = std::nan(""); + us_max_timestamp = std::nan(""); us_min_n_hits_for_centroid = 15; us_qdc_to_gev = 0.0151; us_min_hit_on_bar = 5; @@ -68,6 +68,9 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter us_z_max = 480.0; centroid_min_valid_station = 2; + + scifi_min_timestamp = -0.5; + scifi_max_timestamp = 1.2; } else if (option == Option::ti18_2022_2023) @@ -82,8 +85,6 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter scifi_y_max = 60.0; scifi_z_min = 280.0; scifi_z_max = 360.0; - scifi_min_timestamp = -0.5; - scifi_max_timestamp = 0.5; us_x_min = -80.0; us_x_max = 0.0; @@ -93,6 +94,9 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter us_z_max = 480.0; centroid_min_valid_station = 2; + + scifi_min_timestamp = -0.5; + scifi_max_timestamp = 1.2; } else if (option == Option::test_beam_2023) @@ -108,13 +112,17 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter scifi_y_max = 55.0; scifi_z_min = 310.0; scifi_z_max = 360.0; - + scifi_min_timestamp = -0.5; + scifi_max_timestamp = 0.5; + us_x_min = -80.0; us_x_max = 5.0; us_y_min = 10.0; us_y_max = 80.0; us_z_min = 370.0; us_z_max = 480.0; + us_min_timestamp = -0.5; + us_max_timestamp = 3.0; centroid_min_valid_station = 0; } From a1ee0da85799366898e1b9408fa20040763185fb Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 11 Nov 2025 09:12:34 +0100 Subject: [PATCH 36/99] Finish 2025 det alignment: add consts for targets 256-258 --- shipLHC/modifyGeoFileDict.py | 50 +++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index ddc6a3cdba..3a198257c1 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -77,6 +77,8 @@ def modifyDicts(year=2024): constants['t_11576']= [-6.41,-6.48,-6.72,-6.78,-6.43,-6.53,-5.94,-6.06,-6.19,-6.36,-6.00,-6.17,-8.17,-8.20,-8.30,-8.49,-8.08,-8.16,-8.11,-8.21] constants['t_11676']= [-6.42,-6.47,-6.74,-6.79,-6.43,-6.53,-5.95,-6.06,-6.21,-6.35,-6.01,-6.17,-8.16,-8.22,-8.32,-8.50,-8.09,-8.17,-8.13,-8.22] constants['t_11795']= [-6.43,-6.45,-6.71,-6.79,-6.43,-6.52,-5.99,-6.09,-6.15,-6.32,-5.98,-6.13,-8.17,-8.25,-8.29,-8.48,-8.08,-8.14,-8.16,-8.24] + constants['t_12021']= [-6.46,-6.53,-6.79,-6.89,-6.49,-6.62,-6.02,-6.13,-6.24,-6.40,-6.05,-6.22,-8.21,-8.28,-8.38,-8.57,-8.15,-8.25,-8.22,-8.32] + constants['t_12123']= [-6.60,-6.65,-6.74,-6.84,-6.46,-6.57,-6.06,-6.13,-6.15,-6.33,-6.00,-6.16,-8.20,-8.26,-8.30,-8.49,-8.13,-8.23,-8.27,-8.40] ds_time_aligment_consts = { 2022: {"t_0":0.000, "t_4361":0.082, "t_5117":0.085}, 2023: {"t_0":0.000, "t_5478":0.082, "t_6208":0.086, "t_6443":0.082, "t_6677":0.084}, @@ -84,7 +86,7 @@ def modifyDicts(year=2024): "t_9156":0.083, "t_9286":0.082, "t_9379":0.083, "t_9462":0.083, "t_9613":0.082, "t_9692":0.078, "t_9882":0.084, "t_10012":0.085}, 2025: {"t_0": 0.000, "t_10423":0.083, "t_11158":0.082, "t_11576":0.079, "t_11676":0.080, - "t_11795": 0.079} + "t_11795": 0.079, "t_12021":0.077, "t_12123":0.081} } slopes_dict = ds_time_aligment_consts[year] #time delay corrections first order, only for DS at the moment @@ -219,13 +221,23 @@ def modifyDicts(year=2024): -1.078*u.ns, 0.000*u.ns, -0.014*u.ns, 0.569*u.ns, 0.930*u.ns, -0.501*u.ns, 1.000*u.ns, -0.417*u.ns, 0.000*u.ns, -1.360*u.ns, 0.336*u.ns, -2.079*u.ns, -1.514*u.ns, -0.775*u.ns, -0.736*u.ns, 0.000*u.ns, -1.317*u.ns, -1.076*u.ns, 0.296*u.ns, -1.402*u.ns, 0.989*u.ns] + constants['t_12021']=[ 0.000*u.ns, 0.000*u.ns, -0.362*u.ns, -0.538*u.ns, 0.199*u.ns, -0.368*u.ns, 0.065*u.ns, + -1.309*u.ns, 0.000*u.ns, 0.621*u.ns, -0.677*u.ns, 0.018*u.ns, 0.642*u.ns, 0.122*u.ns, + -1.107*u.ns, 0.000*u.ns, -0.012*u.ns, 0.655*u.ns, 0.945*u.ns, -0.479*u.ns, 1.043*u.ns, + -0.409*u.ns, 0.000*u.ns, -1.327*u.ns, 0.336*u.ns, -2.151*u.ns, -1.517*u.ns, -0.686*u.ns, + -1.292*u.ns, 0.000*u.ns, -0.719*u.ns, 0.005*u.ns, -2.059*u.ns, -0.669*u.ns, 1.800*u.ns] + constants['t_12123']=[ 0.000*u.ns, 0.000*u.ns, -0.381*u.ns, -0.537*u.ns, 0.162*u.ns, -0.414*u.ns, 0.002*u.ns, + -1.353*u.ns, 0.000*u.ns, 0.614*u.ns, -0.849*u.ns, 0.014*u.ns, 0.634*u.ns, 0.060*u.ns, + -1.092*u.ns, 0.000*u.ns, -0.038*u.ns, 0.568*u.ns, 0.909*u.ns, -0.522*u.ns, 0.968*u.ns, + -0.445*u.ns, 0.000*u.ns, -1.315*u.ns, 0.328*u.ns, -2.095*u.ns, -1.545*u.ns, -0.743*u.ns, + -1.662*u.ns, 0.000*u.ns, -0.410*u.ns, -0.160*u.ns, 1.179*u.ns, -0.530*u.ns, 1.911*u.ns] # scifi_time_aligment_consts = { 2022: ['t_0', 't_4361','t_5117'], 2023: ['t_0', 't_5478', 't_6208', 't_6443', 't_6677'], 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'], - 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795'] + 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795', 't_12021', 't_12123'] } scifi_time_tags = scifi_time_aligment_consts[year] for c in scifi_time_tags: @@ -641,13 +653,45 @@ def modifyDicts(year=2024): 0.48*u.mrad, 0.00*u.mrad, 0.00*u.mrad, 0.32*u.mrad, -0.28*u.mrad, 0.23*u.mrad, -0.20*u.mrad, 0.40*u.mrad, 1.32*u.mrad] + alignment['t_12021']=[ #2025 emulsion run 24 run_256 scifi 4v mat 0 is out + 385.78*u.um, 414.57*u.um, 345.23*u.um, + -188.43*u.um, -125.87*u.um, -75.15*u.um, + -184.34*u.um, -108.25*u.um, -208.02*u.um, + 152.45*u.um, 244.56*u.um, 228.59*u.um, + 1.00*u.um, 48.14*u.um, -56.59*u.um, + 159.00*u.um, 131.02*u.um, 191.70*u.um, + 269.80*u.um, 291.84*u.um, 330.59*u.um, + -433.21*u.um, -465.53*u.um, -428.92*u.um, + -175.32*u.um, -225.90*u.um, -249.68*u.um, + 0.00*u.um, 325.05*u.um, 191.00*u.um, + 0.03*u.mrad, -0.35*u.mrad, -0.13*u.mrad, + 1.22*u.mrad, 0.00*u.mrad, -0.24*u.mrad, + 1.50*u.mrad, 0.10*u.mrad, 0.00*u.mrad, + -1.52*u.mrad, -0.21*u.mrad, 0.21*u.mrad, + 1.83*u.mrad, -0.24*u.mrad, 0.50**u.mrad] + alignment['t_12123']=[ #2025 dummy target run_257 and ion run_258 + 230.00*u.um, 213.75*u.um, 268.00*u.um, + -70.75*u.um, 9.00*u.um, -36.25*u.um, + -101.50*u.um, -91.75*u.um, -118.20*u.um, + -12.25*u.um, 52.50*u.um, -18.60*u.um, + -33.00*u.um, -26.00*u.um, -63.25*u.um, + 58.00*u.um, -7.50*u.um, 56.25*u.um, + -104.00*u.um, -104.25*u.um, -26.50*u.um, + 105.25*u.um, 2.00*u.um, 31.75*u.um, + 238.00*u.um, 216.25*u.um, 214.00*u.um, + -239.25*u.um, 2.00*u.um, -119.75*u.um, + -0.32*u.mrad, -0.01*u.mrad, 0.23*u.mrad, + -0.18*u.mrad, -0.01*u.mrad, 0.23*u.mrad, + 0.45*u.mrad, 0.05*u.mrad, 0.00*u.mrad, + 0.39*u.mrad, -0.09*u.mrad, 0.01*u.mrad, + -0.59*u.mrad, 0.01*u.mrad, 0.00**u.mrad] scifi_spatial_aligment_consts = { 2022: ['t_0', 't_4361','t_4575','t_4855','t_5172'], 2023: ['t_0', 't_5431', 't_6443', 't_6677'], 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'], - 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795'] + 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795', 't_12021', 't_12123'] } scifi_spatial_tags = scifi_spatial_aligment_consts[year] for c in scifi_spatial_tags: From a63c0f783b51572efe17af9058989f45c6da056d Mon Sep 17 00:00:00 2001 From: fmei Date: Wed, 15 Oct 2025 23:15:57 +0200 Subject: [PATCH 37/99] set default for csv tables --- analysis/tools/AnalysisToolsLinkDef.h | 7 ++++--- analysis/tools/sndGeometryGetter.cxx | 10 +++++++--- analysis/tools/sndGeometryGetter.h | 5 +++-- analysis/tools/sndTchainGetter.cxx | 10 +++++++--- analysis/tools/sndTchainGetter.h | 6 ++++-- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/analysis/tools/AnalysisToolsLinkDef.h b/analysis/tools/AnalysisToolsLinkDef.h index f1bc6c6936..07ae4f6fbc 100644 --- a/analysis/tools/AnalysisToolsLinkDef.h +++ b/analysis/tools/AnalysisToolsLinkDef.h @@ -32,10 +32,11 @@ #pragma link C++ function snd::analysis_tools::densityCheck(const TClonesArray &, int, int, int, bool); #pragma link C++ function snd::analysis_tools::showerInteractionWall(const TClonesArray &,const std::map &, int, std::string); #pragma link C++ function snd::analysis_tools::showerInteractionWall(const TClonesArray &, int, std::string); -#pragma link C++ function snd::analysis_tools::GetDataBasePath(const std::string &, int); -#pragma link C++ function snd::analysis_tools::GetTChain(const std::string &, int, int); +#pragma link C++ function snd::analysis_tools::GetDataBasePath(int, std::string); +#pragma link C++ function snd::analysis_tools::GetTChain(int, int, const std::string &); #pragma link C++ function snd::analysis_tools::GetTChain(const std::string &); -#pragma link C++ function snd::analysis_tools::GetGeoPath(const std::string &,int); +#pragma link C++ function snd::analysis_tools::GetGeoPath(int, std::string); +#pragma link C++ function snd::analysis_tools::GetGeometry(int, const std::string &); #pragma link C++ function snd::analysis_tools::GetGeometry(const std::string &); #pragma link C++ function snd::analysis_tools::FillScifi(const snd::Configuration &, TClonesArray *, Scifi *); #pragma link C++ function snd::analysis_tools::FillUS(const snd::Configuration &, TClonesArray *, MuFilter *); diff --git a/analysis/tools/sndGeometryGetter.cxx b/analysis/tools/sndGeometryGetter.cxx index df02ed9970..964e710392 100644 --- a/analysis/tools/sndGeometryGetter.cxx +++ b/analysis/tools/sndGeometryGetter.cxx @@ -3,6 +3,7 @@ #include #include #include +#include #include "Scifi.h" #include "MuFilter.h" @@ -11,8 +12,11 @@ // Get geometry full path, works with test beam too // 2022 constants are included in the 2023 geofile -std::string snd::analysis_tools::GetGeoPath(const std::string& csv_file_path, int run_number) +std::string snd::analysis_tools::GetGeoPath(int run_number, std::string csv_file_path) { + if (csv_file_path.empty()) { + csv_file_path = std::string(getenv("SNDSW_ROOT")) + "/analysis/tools/geo_paths.csv"; + } std::ifstream file(csv_file_path); if (!file.is_open()) { throw std::runtime_error("Could not open CSV file: " + csv_file_path); @@ -59,9 +63,9 @@ std::pair snd::analysis_tools::GetGeometry(const std::strin } // Get SciFi and MuFilter geometries directly from run number -std::pair snd::analysis_tools::GetGeometry(const std::string& csv_file_path, int run_number) +std::pair snd::analysis_tools::GetGeometry(int run_number, const std::string& csv_file_path) { - std::string geometry_path = GetGeoPath(csv_file_path, run_number); + std::string geometry_path = GetGeoPath(run_number, csv_file_path); return GetGeometry(geometry_path); } diff --git a/analysis/tools/sndGeometryGetter.h b/analysis/tools/sndGeometryGetter.h index fcf2e090e0..8b53d276a6 100644 --- a/analysis/tools/sndGeometryGetter.h +++ b/analysis/tools/sndGeometryGetter.h @@ -9,9 +9,10 @@ namespace snd { namespace analysis_tools { - std::string GetGeoPath(const std::string& csv_file_path, int run_number); + // With empty csv_file_path, it gets the official one from the sndsw installation + std::string GetGeoPath(int run_number, std::string csv_file_path = ""); std::pair GetGeometry(const std::string& geometry_path); - std::pair GetGeometry(const std::string& csv_file_path, int run_number); + std::pair GetGeometry(int run_number, const std::string& csv_file_path = ""); } } diff --git a/analysis/tools/sndTchainGetter.cxx b/analysis/tools/sndTchainGetter.cxx index 61677f9df0..fd64e88c1a 100644 --- a/analysis/tools/sndTchainGetter.cxx +++ b/analysis/tools/sndTchainGetter.cxx @@ -5,11 +5,15 @@ #include #include #include +#include #include "TChain.h" #include -std::string snd::analysis_tools::GetDataBasePath(const std::string& csv_file_path, int run_number) { +std::string snd::analysis_tools::GetDataBasePath(int run_number, std::string csv_file_path) { + if (csv_file_path.empty()) { + csv_file_path = std::string(getenv("SNDSW_ROOT")) + "/analysis/tools/data_paths.csv"; + } std::ifstream file(csv_file_path); if (!file.is_open()) { throw std::runtime_error("Could not open CSV file: " + csv_file_path); @@ -38,8 +42,8 @@ std::string snd::analysis_tools::GetDataBasePath(const std::string& csv_file_pat throw std::runtime_error("Run number not found in CSV mapping."); } -std::unique_ptr snd::analysis_tools::GetTChain(const std::string& csv_file_path, int run_number, int n_files){ - std::string base_folder = GetDataBasePath(csv_file_path, run_number); +std::unique_ptr snd::analysis_tools::GetTChain(int run_number, int n_files, const std::string& csv_file_path){ + std::string base_folder = GetDataBasePath(run_number, csv_file_path); auto tchain = std::make_unique("rawConv"); if (n_files == -1) { tchain->Add(Form("%srun_%06d/sndsw_raw-*", base_folder.c_str(), run_number)); diff --git a/analysis/tools/sndTchainGetter.h b/analysis/tools/sndTchainGetter.h index d543bdfeaa..4013689725 100644 --- a/analysis/tools/sndTchainGetter.h +++ b/analysis/tools/sndTchainGetter.h @@ -8,9 +8,11 @@ namespace snd { namespace analysis_tools { - std::unique_ptr GetTChain(const std::string& csv_file_path, int run_number, int n_files = -1); + // With n_files == -1 all run partitions are added to the chain. + // With empty csv_file_path, it gets the official one from the sndsw installation + std::unique_ptr GetTChain(int run_number, int n_files = -1, const std::string& csv_file_path = ""); std::unique_ptr GetTChain(const std::string& file_name); - std::string GetDataBasePath(const std::string& csv_file_path, int run_number); + std::string GetDataBasePath(int run_number, std::string csv_file_path = ""); } } From e296092dc87ded47b555f96b98c678d3c1e2e96b Mon Sep 17 00:00:00 2001 From: fmei Date: Wed, 15 Oct 2025 23:37:45 +0200 Subject: [PATCH 38/99] use large sipm only + use const& --- analysis/tools/sndScifiPlane.cxx | 2 +- analysis/tools/sndScifiPlane.h | 2 +- analysis/tools/sndUSPlane.cxx | 2 +- analysis/tools/sndUSPlane.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/analysis/tools/sndScifiPlane.cxx b/analysis/tools/sndScifiPlane.cxx index 3e8cb3a6de..4bc74b6173 100644 --- a/analysis/tools/sndScifiPlane.cxx +++ b/analysis/tools/sndScifiPlane.cxx @@ -12,7 +12,7 @@ #include "ShipUnit.h" #include "Math/Point3D.h" -snd::analysis_tools::ScifiPlane::ScifiPlane(std::vector snd_hits, Configuration configuration, Scifi *scifi_geometry, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""), std::nan("")), station_(station) +snd::analysis_tools::ScifiPlane::ScifiPlane(std::vector snd_hits, const Configuration &configuration, Scifi *scifi_geometry, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""), std::nan("")), station_(station) { for ( auto snd_hit : snd_hits) { diff --git a/analysis/tools/sndScifiPlane.h b/analysis/tools/sndScifiPlane.h index 077eff9495..5f90a9eecf 100644 --- a/analysis/tools/sndScifiPlane.h +++ b/analysis/tools/sndScifiPlane.h @@ -32,7 +32,7 @@ namespace snd { bool is_x{}; }; - ScifiPlane(std::vector snd_hits, Configuration configuration, Scifi *scifi_geometry, int station); + ScifiPlane(std::vector snd_hits, const Configuration &configuration, Scifi *scifi_geometry, int station); const int GetStation() const { return station_; }; const std::vector GetHits() const { return hits_; }; diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index be7f287adf..214c72823c 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -9,7 +9,7 @@ #include "MuFilter.h" #include "MuFilterHit.h" -snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, Configuration configuration, MuFilter *muon_filter_geometry, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) +snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) { for ( auto mu_hit : snd_hits) { diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h index f5d9a52831..ecda2d2012 100644 --- a/analysis/tools/sndUSPlane.h +++ b/analysis/tools/sndUSPlane.h @@ -45,7 +45,7 @@ namespace snd { bool is_right; }; - USPlane(std::vector snd_hits, Configuration configuration, MuFilter *muon_filter_geometry, int station); + USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station); const sl_pair GetNHits() const; const int GetStation() const { return station_; }; @@ -56,7 +56,7 @@ namespace snd { const rl_pair GetBarQdc(int bar_to_compute) const; const sl_pair GetBarNHits(int bar_to_compute) const; const std::vector GetHits() const { return hits_; }; - double HasShower() const { return static_cast(hits_.size()) >= configuration_.us_min_n_hits_for_centroid; }; + double HasShower() const { return GetNHits().large >= configuration_.us_min_n_hits_for_centroid; }; // The centroid is the qdc-weighted mean of hit positions, considering only hits with positive qdc void FindCentroid(); ROOT::Math::XYZPoint GetCentroid() const { return centroid_; }; From 6d60edd1ffd8ab06210b39bcefa83d3920092697 Mon Sep 17 00:00:00 2001 From: Cristovao Vilela Date: Mon, 13 Oct 2025 16:08:26 +0200 Subject: [PATCH 39/99] Adding python script to make run lists with DB info. --- analysis/makeRunListDB.py | 150 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 analysis/makeRunListDB.py diff --git a/analysis/makeRunListDB.py b/analysis/makeRunListDB.py new file mode 100644 index 0000000000..51b6bee8af --- /dev/null +++ b/analysis/makeRunListDB.py @@ -0,0 +1,150 @@ +import pymongo +import argparse +import xml.etree.ElementTree as ET +from datetime import datetime +from collections import defaultdict +import os + +parser = argparse.ArgumentParser( + prog="makeRunListDB", + description="Extracts a list of runs from the SND@LHC DB, matching the conditions given in the arguments. Produces an XML file with the run list and a summary of the selection.") +parser.add_argument("--name", type=str, help="Run list name", required=True) +parser.add_argument("--years", nargs="+", type=int, help="Years to be included, e.g. 2022 2023", required=True) +parser.add_argument("--min_events", type=int, help="Minimum number of events in run", default=0) +parser.add_argument("--min_lumi", type=float, help="Minimum integrated luminosity for a run to be included, in fb-1", default=0.) +parser.add_argument("--min_stable_time", type=float, help="Minimum stable beams time of the corresponding LHC fill for a run to be included, in minutes", default=0.) +parser.add_argument("--particle_B1", type=str, help="Beam 1 particle, e.g. p+ or PB82", required=True) +parser.add_argument("--particle_B2", type=str, help="Beam 2 particle, e.g. p+ or PB82", required=True) +parser.add_argument("--min_energy", type=float, help="Minimum beam energy (in GeV)", default=0.) +parser.add_argument("--max_energy", type=float, help="Maximum beam energy (in GeV)", default=0.) +parser.add_argument("--min_bunches_IP1", type=int, help="Minimum number of bunches colliding at IP1", default=0) +parser.add_argument("--max_bunches_IP1", type=int, help="Maximum number of bunches colliding at IP1", default=0) +parser.add_argument("--include_runs", nargs="+", type=int, help="Runs to include, regardless of the other criteria", default=[]) +parser.add_argument("--exclude_runs", nargs="+", type=int, help="Runs to exclude from the list", default=[]) +args = parser.parse_args() + +client = pymongo.MongoClient("sndrundb.cern.ch") +db = client.sndrundb + +pipeline = [] + +# Get the run list corresponding to the selected years +pipeline.append({"$match": {"$expr": {"$in": [{"$year": "$start"}, args.years]}}}) + +# Select runs with a minimum of min_events events: +if args.min_events > 0: + pipeline.append({"$match": {"events": {"$gt": args.min_events}}}) + +# Combine data fill from the LPC +pipeline.append({"$lookup": {"from": "FILL_LPC", "localField": "fill", "foreignField": "_id", "as": "LPC"}}) + +if args.min_lumi > 0.: + # Select runs with at least min_lumi integrated luminosity + pipeline.append({"$match": {"LPC.ATLAS_Int_Lumi": {"$gt": args.min_lumi*1e6}}}) + +if args.min_stable_time > 0.: + # Select runs with at least min_stable_time minutes of stable beams + pipeline.append({"$match": {"LPC.Stable_time": {"$gt": args.min_stable_time/60.}}}) + +# Select B1 particle +pipeline.append({"$match": {"LPC.Particle_B1": args.particle_B1}}) + +# Select B2 particle +pipeline.append({"$match": {"LPC.Particle_B2": args.particle_B2}}) + +if args.min_energy > 0: + # Select runs with args.min_energy energy + pipeline.append({"$match": {"LPC.Energy": {"$gt": args.min_energy}}}) + +if args.max_energy > 0: + # Select runs with args.max_energy energy + pipeline.append({"$match": {"LPC.Energy": {"$lt": args.max_energy}}}) + +if args.min_bunches_IP1 > 0: + # Select runs with at least min_bunches_IP1 bunches colliding at IP1 + pipeline.append({"$match": {"LPC.Coll_IP_1_5": {"$gt": args.min_bunches_IP1}}}) + +if args.max_bunches_IP1 > 0: + # Select runs with at most max_bunches_IP1 bunches colliding at IP1 + pipeline.append({"$match": {"LPC.Coll_IP_1_5": {"$lt": args.max_bunches_IP1}}}) + +if len(args.exclude_runs) > 0: + pipeline.append({"$match": {"runNumber": {"$nin" : args.exclude_runs}}}) + +# Expression for Calculating run length from start and stop datetimes +run_length_expr = {"$dateDiff": {"startDate": "$start", "endDate": "$stop", "unit": "minute"}} + +# Extract the following data from the DB +projection = {"$project":{"_id": 0, # Do not extract DB entry ID + "run_number": "$runNumber", # Run number + "n_events": "$events", # Number of events + "start": 1, # Start date + "end": 1, # End date + "duration": run_length_expr, # Run duration + "n_files": {"$size": "$files"}, # Number of files + "path": {"$first": "$files.file"}, # Path of the first file + "fill_number": "$fill", # Fill number + "fill_int_lumi": {"$first": "$LPC.ATLAS_Int_Lumi"}, # Integrated luminosity + "fill_stable_time" : {"$first": "$LPC.Stable_time"}} # Stable beams duration + } + +pipeline.append(projection) + +result = list(db["EcsData"].aggregate(pipeline)) + +if len(args.include_runs) > 0: + include_pipeline = [] + include_pipeline.append({"$match": {"runNumber": {"$in": args.include_runs}}}) + include_pipeline.append(projection) + + include_result = list(db["EcsData"].aggregate(include_pipeline)) + + result.extend(list(include_result)) + +result.sort(key=lambda x: x["run_number"]) + +# Get the time now +now = datetime.now() + +# Format into xml tree +root = ET.Element("runlist") + +meta_data = ET.SubElement(root, "meta") +ET.SubElement(meta_data, "name").text = args.name +ET.SubElement(meta_data, "datetime").text = now.strftime("%Y-%m-%dT%H:%M:%S.%f") +selection = ET.SubElement(meta_data, "selection") +ET.SubElement(selection, "years").text = ','.join([str(y) for y in args.years]) +for criterion in ["min_events", "min_lumi", "min_stable_time", "particle_B1", "particle_B2", "min_energy", "max_energy", "min_bunches_IP1", "max_bunches_IP1", "exclude_runs", "include_runs"]: + ET.SubElement(selection, criterion).text = str(getattr(args, criterion)) +runs = ET.SubElement(root, "runs") + +# Counters for summary statistics +n_runs = 0 +totals = defaultdict(int) + +# Run loop +for run in result: + this_run = ET.SubElement(runs, "run") + totals["n_runs"] += 1 + for field_name in ["run_number", "start", "end", "n_events", "duration", "n_files", "fill_number", "fill_int_lumi", "fill_stable_time", "path"]: + try: + data = run[field_name] + except KeyError: + continue + + if field_name == "path": + data = os.path.dirname(data) + + ET.SubElement(this_run, field_name).text = str(data) + + if field_name not in ["run_number", "start", "end", "fill_number", "path", "fill_int_lumi", "fill_stable_time"] and data is not None: + totals["tot_"+field_name] += data + +stats = ET.SubElement(meta_data, "statistics") +for key, data in totals.items(): + ET.SubElement(stats, key).text = str(data) + +# Write to xml file +tree = ET.ElementTree(root) +ET.indent(tree, space=" ") +tree.write(args.name+"_"+str(now.timestamp())+".xml", encoding="utf-8", xml_declaration=True) From 5da07044317a9d601db3937507ba8631134aee26 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 24 Oct 2025 18:59:16 +0200 Subject: [PATCH 40/99] ScifiPlane: Improve centroid error estimation Use propagation of uncertainty respecting the weight(=qdc) per hit. --- analysis/tools/sndScifiPlane.cxx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/analysis/tools/sndScifiPlane.cxx b/analysis/tools/sndScifiPlane.cxx index 4bc74b6173..ef1dfe0671 100644 --- a/analysis/tools/sndScifiPlane.cxx +++ b/analysis/tools/sndScifiPlane.cxx @@ -222,8 +222,8 @@ snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPlane void snd::analysis_tools::ScifiPlane::FindCentroid() { centroid_.SetXYZ(0, 0, 0); - double tot_qdc_x{0}; - double tot_qdc_y{0}; + double tot_qdc_x{0}, sum_qdc2_x{0.0}; + double tot_qdc_y{0}, sum_qdc2_y{0.0}; std::vector cleaned_hits = hits_; cleaned_hits.erase(std::remove_if(cleaned_hits.begin(), cleaned_hits.end(), @@ -246,18 +246,25 @@ void snd::analysis_tools::ScifiPlane::FindCentroid() { centroid_.SetX(centroid_.X() + hit.x * hit.qdc); tot_qdc_x += hit.qdc; + sum_qdc2_x += hit.qdc*hit.qdc; } else { centroid_.SetY(centroid_.Y() + hit.y * hit.qdc); tot_qdc_y += hit.qdc; + sum_qdc2_y += hit.qdc*hit.qdc; } centroid_.SetZ(centroid_.Z() + hit.z * hit.qdc); } } centroid_ = ROOT::Math::XYZPoint((tot_qdc_x > 0) ? centroid_.X() / tot_qdc_x : std::nan(""), (tot_qdc_y > 0) ? centroid_.Y() / tot_qdc_y : std::nan(""), (tot_qdc_x+tot_qdc_y > 0) ? centroid_.Z() / (tot_qdc_x+tot_qdc_y) : std::nan("")); - centroid_error_ = ROOT::Math::XYZPoint(configuration_.scifi_centroid_error_x, configuration_.scifi_centroid_error_y, configuration_.scifi_centroid_error_z); + auto qdc_x_error_scaler = sqrt(sum_qdc2_x)/tot_qdc_x; + auto qdc_y_error_scaler = sqrt(sum_qdc2_y)/tot_qdc_y; + auto qdc_z_error_scaler = sqrt(sum_qdc2_x+sum_qdc2_y)/(tot_qdc_x+tot_qdc_y); + centroid_error_ = ROOT::Math::XYZPoint(configuration_.scifi_centroid_error_x*qdc_x_error_scaler, + configuration_.scifi_centroid_error_y*qdc_y_error_scaler, + configuration_.scifi_centroid_error_z*qdc_z_error_scaler); } const snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::ScifiPlane::GetTotQdc(bool only_positive) const @@ -280,4 +287,4 @@ const snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::Scif energy.y = qdc.y * configuration_.scifi_qdc_to_gev; return energy; -} \ No newline at end of file +} From 14904e7ca2bbacec83014a0115b5033f7bc35ad4 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 24 Oct 2025 19:06:32 +0200 Subject: [PATCH 41/99] USPlane: Fix FindCentroid method Use the center of gravity of hits in the plane that have positive qdc and are recorded by large SiPM. --- analysis/tools/sndUSPlane.cxx | 50 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index 214c72823c..80fc1a053b 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -37,36 +37,36 @@ snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const void snd::analysis_tools::USPlane::FindCentroid() { + // select large SiPM channels with positive qdc + std::vector cleaned_hits = hits_; + + cleaned_hits.erase(std::remove_if(cleaned_hits.begin(), cleaned_hits.end(), + [&](auto &hit) + { return (hit.qdc <= 0 || hit.is_large == false); }), + cleaned_hits.end()); + // min number of hit in the plane to attempt to find a centroid - if (static_cast(hits_.size()) < configuration_.us_min_n_hits_for_centroid) + if (static_cast(cleaned_hits.size()) < configuration_.us_min_n_hits_for_centroid) { // std::cout<<"Not enough hits in US plane " << station_ <<" to find centroid\n"; return; } - - double total_qdc = GetTotQdc().large; - if (total_qdc > 0.0) - { - for (const auto &hit : hits_) - { - // weigthed sum - double weighted_sum_x{0.0}, weighted_sum_y{0.0}, weighted_sum_z{0.0}; - double total_qdc_positive{0.0}; - if (hit.qdc > 0.0) - { - weighted_sum_x += hit.x * hit.qdc; - weighted_sum_y += hit.y * hit.qdc; - weighted_sum_z += hit.z * hit.qdc; - total_qdc_positive += hit.qdc; - } - - double x = weighted_sum_x / total_qdc_positive; - double y = weighted_sum_y / total_qdc_positive; - double z = weighted_sum_z / total_qdc_positive; - centroid_.SetXYZ(x, y, z); - } + // weigthed sum calculated per plane + double weighted_sum_x{0.0}, weighted_sum_y{0.0}, weighted_sum_z{0.0}; + double total_qdc_positive{0.0}; + // loop over hits in the plane + for (const auto &hit : cleaned_hits) + { + weighted_sum_x += hit.x * hit.qdc; + weighted_sum_y += hit.y * hit.qdc; + weighted_sum_z += hit.z * hit.qdc; + total_qdc_positive += hit.qdc; } + weighted_sum_x /= total_qdc_positive; + weighted_sum_y /= total_qdc_positive; + weighted_sum_z /= total_qdc_positive; + centroid_.SetXYZ(weighted_sum_x, weighted_sum_y, weighted_sum_z); centroid_error_.SetXYZ(configuration_.us_centroid_error_x, configuration_.us_centroid_error_y, configuration_.us_centroid_error_z); } @@ -170,11 +170,11 @@ const snd::analysis_tools::USPlane::sl_pair snd::analysis_tools::USPlane::G return counts; } - const int snd::analysis_tools::USPlane::GetNHitBars() const{ int count{0}; for (int bar{0}; bar < configuration_.us_bar_per_station; ++bar) { if (GetBarNHits(bar).large > configuration_.us_min_hit_on_bar) count++; } return count; -} \ No newline at end of file +} + From c5863bee4e9ddbe4f5a41f917dbb880b3e884c57 Mon Sep 17 00:00:00 2001 From: siilieva Date: Wed, 12 Nov 2025 17:54:27 +0100 Subject: [PATCH 42/99] USPlane: Improve centroid error estimation Use propagation of uncertainty respecting the weight(=qdc) per hit. --- analysis/tools/sndUSPlane.cxx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index 80fc1a053b..d0b69b0945 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -54,7 +54,7 @@ void snd::analysis_tools::USPlane::FindCentroid() // weigthed sum calculated per plane double weighted_sum_x{0.0}, weighted_sum_y{0.0}, weighted_sum_z{0.0}; - double total_qdc_positive{0.0}; + double total_qdc_positive{0.0}, sum_qdc2_positive{0.0}; // loop over hits in the plane for (const auto &hit : cleaned_hits) { @@ -62,12 +62,16 @@ void snd::analysis_tools::USPlane::FindCentroid() weighted_sum_y += hit.y * hit.qdc; weighted_sum_z += hit.z * hit.qdc; total_qdc_positive += hit.qdc; + sum_qdc2_positive += hit.qdc*hit.qdc; } weighted_sum_x /= total_qdc_positive; weighted_sum_y /= total_qdc_positive; weighted_sum_z /= total_qdc_positive; centroid_.SetXYZ(weighted_sum_x, weighted_sum_y, weighted_sum_z); - centroid_error_.SetXYZ(configuration_.us_centroid_error_x, configuration_.us_centroid_error_y, configuration_.us_centroid_error_z); + auto qdc_error_scaler = sqrt(sum_qdc2_positive)/total_qdc_positive; + centroid_error_.SetXYZ(configuration_.us_centroid_error_x*qdc_error_scaler, + configuration_.us_centroid_error_y*qdc_error_scaler, + configuration_.us_centroid_error_z*qdc_error_scaler); } const snd::analysis_tools::USPlane::sl_pair snd::analysis_tools::USPlane::GetTotQdc() const From 3732af908a3a0d2ad8168079e7d43e67eff6b0e6 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 24 Oct 2025 19:19:01 +0200 Subject: [PATCH 43/99] USPlane: Utilize left/right measurements to determine X position along bar Using the two-sided US readout one can determine the X position along the horizontal bar. Use this for the US plane objects and adjust the X measurement error. For now a conservative value - needs calibration! --- analysis/tools/sndConfiguration.cxx | 6 ++++-- analysis/tools/sndConfiguration.h | 5 ++++- analysis/tools/sndPlaneTools.cxx | 6 +++--- analysis/tools/sndPlaneTools.h | 4 ++-- analysis/tools/sndUSPlane.cxx | 20 +++++++++++++------- analysis/tools/sndUSPlane.h | 4 ++-- 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/analysis/tools/sndConfiguration.cxx b/analysis/tools/sndConfiguration.cxx index b9ee37f0bd..b874c1fa47 100644 --- a/analysis/tools/sndConfiguration.cxx +++ b/analysis/tools/sndConfiguration.cxx @@ -24,7 +24,9 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter us_bar_per_station = muon_filter_geometry->GetConfParI("MuFilter/NUpstreamBars"); us_n_sipm_per_bar = muon_filter_geometry->GetConfParI("MuFilter/UpstreamnSiPMs") * muon_filter_geometry->GetConfParI("MuFilter/UpstreamnSides"); us_n_channels_per_station = us_bar_per_station * us_n_sipm_per_bar; - us_centroid_error_x = muon_filter_geometry->GetConfParF("MuFilter/UpstreamBarX") / std::sqrt(12); + us_bar_length = muon_filter_geometry->GetConfParF("MuFilter/UpstreamBarX"); + us_signal_speed = muon_filter_geometry->GetConfParF("MuFilter/VandUpPropSpeed"); + us_centroid_error_x = 3.;//estimate based on PMU data, no time calibration us_centroid_error_y = muon_filter_geometry->GetConfParF("MuFilter/UpstreamBarY") / std::sqrt(12); us_centroid_error_z = muon_filter_geometry->GetConfParF("MuFilter/UpstreamBarZ"); @@ -173,4 +175,4 @@ snd::Configuration::Option snd::Configuration::GetOption(int run_number) std::cout << "Choosing option >>>>>>>>>>\t ti18_2024_2025 \t<<<<<<<<<<" < snd::analysis_tools::FillScifi(cons } -std::vector snd::analysis_tools::FillUS(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry) +std::vector snd::analysis_tools::FillUS(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool isMC) { std::vector us_planes; @@ -55,7 +55,7 @@ std::vector snd::analysis_tools::FillUS(const snd: else throw std::runtime_error{"Invalid US plane"}; } for (int st{0}; st < n_station; ++st) { - us_planes.emplace_back(snd::analysis_tools::USPlane(plane_hits[st], configuration, mufilter_geometry, st+1)); + us_planes.emplace_back(snd::analysis_tools::USPlane(plane_hits[st], configuration, mufilter_geometry, st+1, isMC)); } return us_planes; -} \ No newline at end of file +} diff --git a/analysis/tools/sndPlaneTools.h b/analysis/tools/sndPlaneTools.h index 3b3ee9fd66..9c93ba674a 100644 --- a/analysis/tools/sndPlaneTools.h +++ b/analysis/tools/sndPlaneTools.h @@ -14,8 +14,8 @@ namespace snd { namespace analysis_tools { // Produce scifi and us planes from data std::vector FillScifi(const Configuration &configuration, TClonesArray *sf_hits, Scifi *scifi_geometry); - std::vector FillUS(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry); + std::vector FillUS(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool isMC=false); } } -#endif \ No newline at end of file +#endif diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index d0b69b0945..5ebb677676 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -8,26 +8,32 @@ #include "TVector3.h" #include "MuFilter.h" #include "MuFilterHit.h" +#include "ShipUnit.h" -snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) +snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool isMC) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) { for ( auto mu_hit : snd_hits) { + TVector3 A, B; + int detectorID = mu_hit->GetDetectorID(); + muon_filter_geometry->GetPosition(detectorID, A, B); for (int i{0}; i < 16; ++i) { if (mu_hit->isMasked(i) || mu_hit->GetSignal(i) < -990.) continue; USHit hit; - hit.bar = static_cast(mu_hit->GetDetectorID() % 1000); + hit.bar = static_cast(detectorID % 1000); hit.channel_index = 16 * hit.bar + i; hit.timestamp = mu_hit->GetTime(i); hit.qdc = mu_hit->GetSignal(i); hit.is_large = !mu_hit->isShort(i); - - TVector3 A, B; - int detectorID = mu_hit->GetDetectorID(); - muon_filter_geometry->GetPosition(detectorID, A, B); hit.is_right = i > 7 ? true : false; - hit.x = hit.is_right ? B.X() : A.X(); + + // use the left and right measurements to calculate the x coordinate along the bar + float timeConversion = 1.; + if (!isMC) { + timeConversion = ShipUnit::snd_TDC2ns; + } + hit.x = A.X() - 0.5*(mu_hit->GetDeltaT()*timeConversion*configuration_.us_signal_speed+configuration_.us_bar_length); hit.y = A.Y(); hit.z = A.Z(); hits_.push_back(hit); diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h index ecda2d2012..03b192414d 100644 --- a/analysis/tools/sndUSPlane.h +++ b/analysis/tools/sndUSPlane.h @@ -45,7 +45,7 @@ namespace snd { bool is_right; }; - USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station); + USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool isMC=false); const sl_pair GetNHits() const; const int GetStation() const { return station_; }; @@ -76,4 +76,4 @@ namespace snd { } } -#endif \ No newline at end of file +#endif From 38f5e5a43e21a436561365fd61f455cacb6ebd9d Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 24 Oct 2025 19:26:28 +0200 Subject: [PATCH 44/99] 2dED: Add option to plot shower direction --- shipLHC/scripts/2dEventDisplay.py | 78 +++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/shipLHC/scripts/2dEventDisplay.py b/shipLHC/scripts/2dEventDisplay.py index 9e9571fb56..6b24914de6 100644 --- a/shipLHC/scripts/2dEventDisplay.py +++ b/shipLHC/scripts/2dEventDisplay.py @@ -50,6 +50,8 @@ def pyExit(): parser.add_argument("-par", "--parFile", dest="parFile", help="parameter file", default=os.environ['SNDSW_ROOT']+"/python/TrackingParams.xml") parser.add_argument("-hf", "--HoughSpaceFormat", dest="HspaceFormat", help="Hough space representation. Should match the 'Hough_space_format' name in parFile, use quotes", default='linearSlopeIntercept') +parser.add_argument("--shower_dir", dest="drawShowerDir", help="Draw shower direction if a shower is found", action="store_true") + options = parser.parse_args() resolution_factor = 1 @@ -444,6 +446,7 @@ def loopEvents( digis = [] if event.FindBranch("Digi_ScifiHits"): + scifi_digis = event.Digi_ScifiHits method = 0 if FilterScifiHits!=None and FilterScifiHits!="default": filter_parameters = {k: FilterScifiHits[k] for k in important_keys if k in FilterScifiHits} @@ -461,11 +464,23 @@ def loopEvents( selection_parameters["max_x"] = float(filter_parameters["max_x"]) selection_parameters["time_lower_range"] = float(filter_parameters["time_lower_range"]) selection_parameters["time_upper_range"] = float(filter_parameters["time_upper_range"]) - digis.append(ROOT.snd.analysis_tools.filterScifiHits(event.Digi_ScifiHits,selection_parameters,method,setup,mc)) + scifi_digis = ROOT.snd.analysis_tools.filterScifiHits(event.Digi_ScifiHits,selection_parameters,method,setup,mc) else: if FilterScifiHits: logging.warning(Setup+" is not supported for the time-filtering of SciFi hits, using all hits instead.") - digis.append(event.Digi_ScifiHits) + digis.append(scifi_digis) + run_conf = ROOT.snd.Configuration.Option.ti18_2022_2023 + if Setup=='TI18' or Setup=="H6": + run_conf = ROOT.snd.Configuration.Option.ti18_2022_2023 + elif Setup == "H8": + ROOT.snd.Configuration.Option.test_beam_2023 + elif Setup == "H4": + ROOT.snd.Configuration.Option.test_beam_2024 + else: + print("Going for default setup TI18") + configuration = ROOT.snd.Configuration(run_conf, geo.modules['Scifi'], geo.modules['MuFilter']) + scifi_planes = ROOT.snd.analysis_tools.FillScifi(configuration, scifi_digis, geo.modules['Scifi']) + us_planes = ROOT.snd.analysis_tools.FillUS(configuration, event.Digi_MuFilterHits, geo.modules['MuFilter'], mc) if event.FindBranch("Digi_MuFilterHits"): digis.append(event.Digi_MuFilterHits) if event.FindBranch("Digi_MuFilterHit"): digis.append(event.Digi_MuFilterHit) empty = True @@ -533,10 +548,16 @@ def loopEvents( nav.MasterToLocal(globA, locA) Z = X[2] if digi.isVertical(): + # only using hits with positive qdc for centroids, so only show such + if options.drawShowerDir and system==0 and digi.GetSignal(0)<0: + continue collection = 'hitCollectionX' Y = locA[0] sY = detSize[system][0] else: + # only using hits with positive qdc for centroids, so only show such + if options.drawShowerDir and system==0 and digi.GetSignal(0)<0: + continue collection = 'hitCollectionY' Y = locA[1] sY = detSize[system][1] @@ -631,6 +652,31 @@ def loopEvents( if withTiming: timingOfEvent() addTrack(OT) + # try finding shower direction and intercept + if options.drawShowerDir: + sh_scifi_planes, sh_us_planes = ROOT.snd.analysis_tools.GetShoweringPlanes(scifi_planes, us_planes) + ref_point, shower_direction = ROOT.snd.analysis_tools.GetShowerInterceptAndDirection(configuration, sh_scifi_planes, sh_us_planes) + is_nan = np.isnan([ref_point.X(), ref_point.Y(), ref_point.Z(), shower_direction.X(), shower_direction.Y(), shower_direction.Z()]).any() + if is_nan: + print("Found shower direction and/or intercept contain NaN value") + # try finding the shower origin + else: + shower_start = 10*ROOT.snd.analysis_tools.GetScifiShowerStart(scifi_planes) + if shower_start<0: + print("Could not find shower start in SciFi, going for US") + shower_start = 100*ROOT.snd.analysis_tools.GetUSShowerStart(us_planes) + if shower_start<0: + print("Could not find shower start in SciFi or in US") + else: + for k in proj: + if k==1: + drawShowerAxis(h['simpleDisplay'], k, shower_start, ref_point.X(), + shower_direction.X()/shower_direction.Z()) + else: + drawShowerAxis(h['simpleDisplay'], k, shower_start, ref_point.Y(), + shower_direction.Y()/shower_direction.Z()) + h['simpleDisplay'].Update() + if option == "2tracks": rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=0.5) if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=0.75) @@ -861,6 +907,17 @@ def drawDetectors(): for C in P: M[C] = array('d',[0,0,0]) nav.LocalToMaster(P[C],M[C]) + if "volVetoPlane_0" in node: + h['veto0_z'] = M['LeftBottom'][2] + if "ScifiVolume" in node: + for st in range(1,si.nscifi+1): + if f"{st}_{st}000000" in node: + h[f"scifi{st}_z"] = M['LeftBottom'][2] + if "volMuUpstreamDet" in node: + for st in range(mi.NUpstreamPlanes): + if f"{st}_{st+mi.NVetoPlanes}" in node: + h[f"us{st+1}_z"] = M['LeftBottom'][2] + h[node+p] = ROOT.TPolyLine() X = h[node+p] c = proj[p] @@ -1221,5 +1278,18 @@ def drawCollisionAxis(pad, k): h[line_name].Draw() h[text_name].Draw() - - +def drawShowerAxis(pad, k, shower_start, ref_point, shower_direction): + line_name= "shower_dir_" + str(k) + if shower_start<100: + zpos_name='scifi'+str(shower_start//10) + else: + zpos_name='us'+str(shower_start//100) + h[line_name] = ROOT.TLine(h[zpos_name+'_z'], + ref_point+shower_direction*h[zpos_name+'_z'], + h['zmax'], + ref_point+shower_direction*h['zmax']) + h[line_name].SetLineColor(ROOT.kPink-6) + h[line_name].SetLineStyle(3) + h[line_name].SetLineWidth(5) + pad.cd(k) + h[line_name].Draw() From 5a5846e78264a0faa7cc30e7fe7ae0a0eb9d2a34 Mon Sep 17 00:00:00 2001 From: siilieva Date: Wed, 29 Oct 2025 15:25:09 +0100 Subject: [PATCH 45/99] 2dED: Plot tracks starting from most upstream Veto --- shipLHC/scripts/2dEventDisplay.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shipLHC/scripts/2dEventDisplay.py b/shipLHC/scripts/2dEventDisplay.py index 6b24914de6..de10409b23 100644 --- a/shipLHC/scripts/2dEventDisplay.py +++ b/shipLHC/scripts/2dEventDisplay.py @@ -724,7 +724,8 @@ def addTrack(OT,scifi=False): for p in [0,1]: h['aLine'+str(nTrack*10+p)] = ROOT.TGraph() - zEx = xax.GetBinCenter(1) + # draw the track line starting from the most upstream Veto plane + zEx = h['veto0_z'] mom = aTrack.getFittedState().getMom() pos = aTrack.getFittedState().getPos() lam = (zEx-pos.z())/mom.z() From 5b1186f9fe1a6c7750e897f409d73bd67ef7f0d4 Mon Sep 17 00:00:00 2001 From: siilieva Date: Thu, 13 Nov 2025 12:40:35 +0100 Subject: [PATCH 46/99] 2dED: change the canvas size for better readibility when saving to file --- shipLHC/scripts/2dEventDisplay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shipLHC/scripts/2dEventDisplay.py b/shipLHC/scripts/2dEventDisplay.py index de10409b23..0aa16f97a2 100644 --- a/shipLHC/scripts/2dEventDisplay.py +++ b/shipLHC/scripts/2dEventDisplay.py @@ -342,7 +342,7 @@ def loopEvents( logging.warning("Ignoring provided keys other than "+str(all_keys)) if 'simpleDisplay' not in h: - ut.bookCanvas(h,key='simpleDisplay',title='simple event display',nx=1200,ny=1600,cx=1,cy=2) + ut.bookCanvas(h,key='simpleDisplay',title='simple event display',nx=1200,ny=1016,cx=1,cy=2) h['simpleDisplay'].cd(1) # TI18 coordinate system From eb5c56a4e9e2a1d35f73aa10d0104ebdf462c8fe Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 17 Nov 2025 14:44:08 +0100 Subject: [PATCH 47/99] MuFilterHit:Fix GetImpactT method Wrong name, MuFilterDet, instead of the correct,MuFilter, was used to extract detector constants. Cosmetic fix in floor function formatting, has no effect for the computation. --- shipLHC/MuFilterHit.cxx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shipLHC/MuFilterHit.cxx b/shipLHC/MuFilterHit.cxx index a871785c7b..746ead2e7a 100644 --- a/shipLHC/MuFilterHit.cxx +++ b/shipLHC/MuFilterHit.cxx @@ -237,12 +237,12 @@ Float_t MuFilterHit::GetImpactT(Bool_t mask) Float_t dT = -999.; Float_t dL; MuFilter* MuFilterDet = dynamic_cast (gROOT->GetListOfGlobals()->FindObject("MuFilter")); - if (floor(fDetectorID/10000==3)) { - dL = MuFilterDet->GetConfParF("MuFilterDet/DownstreamBarX") / MuFilterDet->GetConfParF("MuFilter/DsPropSpeed");} - else if (floor(fDetectorID/10000==2)) { - dL = MuFilterDet->GetConfParF("MuFilterDet/UpstreamBarX") / MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed");} + if (floor(fDetectorID/10000)==3) { + dL = MuFilterDet->GetConfParF("MuFilter/DownstreamBarX") / MuFilterDet->GetConfParF("MuFilter/DsPropSpeed");} + else if (floor(fDetectorID/10000)==2) { + dL = MuFilterDet->GetConfParF("MuFilter/UpstreamBarX") / MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed");} else { - dL = MuFilterDet->GetConfParF("MuFilterDet/VetoBarX") / MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed");} + dL = MuFilterDet->GetConfParF("MuFilter/VetoBarX") / MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed");} for (unsigned int s=0; s Date: Mon, 17 Nov 2025 14:50:52 +0100 Subject: [PATCH 48/99] MuFilterHit: Add GetImpactXpos method Facilitate left/right readout to obtain an estimate of the impact X position along the bar using the average of L/R times. This method is reliable for single muons, in case of showers the result is a rough estimate by construction. --- shipLHC/MuFilterHit.cxx | 32 +++++++++++++++++++++++++++++++- shipLHC/MuFilterHit.h | 1 + 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/shipLHC/MuFilterHit.cxx b/shipLHC/MuFilterHit.cxx index 746ead2e7a..a0c516354c 100644 --- a/shipLHC/MuFilterHit.cxx +++ b/shipLHC/MuFilterHit.cxx @@ -1,5 +1,6 @@ #include "MuFilterHit.h" #include "MuFilter.h" +#include "ShipUnit.h" #include "TROOT.h" #include "FairRunSim.h" #include "TGeoNavigator.h" @@ -256,11 +257,40 @@ Float_t MuFilterHit::GetImpactT(Bool_t mask) } } if (count[0]>0 && count[1]>0) { - dT = (mean[0]/count[0] + mean[1]/count[1])/2.*6.25 - dL/2.; // TDC to ns = 6.25 + dT = (mean[0]/count[0] + mean[1]/count[1])/2.*ShipUnit::snd_TDC2ns - dL/2.; // TDC to ns = 6.25 } return dT; } +// ----- Public method Get position of impact point along the bar ----------------- +Float_t MuFilterHit::GetImpactXpos(Bool_t mask, Bool_t isMC) +{ + if ( nSides!=2 ){ + return -999.; + } + Float_t dT = GetDeltaT(mask); + if (dT==-999.){ + return -999.; + } + + MuFilter* MuFilterDet = dynamic_cast (gROOT->GetListOfGlobals()->FindObject("MuFilter")); + Float_t bar_length = MuFilterDet->GetConfParF("MuFilter/UpstreamBarX"); + Float_t signal_speed = MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed"); + if (floor(fDetectorID/10000)==3) { + signal_speed = MuFilterDet->GetConfParF("MuFilter/DsPropSpeed"); + bar_length = MuFilterDet->GetConfParF("MuFilter/DownstreamBarX"); + } + else if (floor(fDetectorID/10000)==2) { + bar_length = MuFilterDet->GetConfParF("MuFilter/UpstreamBarX"); + } + else { + bar_length = MuFilterDet->GetConfParF("MuFilter/VetoBarX"); + } + double timeConversion = 1.; + if (!isMC) timeConversion = ShipUnit::snd_TDC2ns; + return 0.5*(bar_length + dT*timeConversion*signal_speed); +} + std::map MuFilterHit::SumOfSignals(Bool_t mask) { /* use cases, for Veto and DS small/large ignored diff --git a/shipLHC/MuFilterHit.h b/shipLHC/MuFilterHit.h index c448a7f015..dc97edb1e9 100644 --- a/shipLHC/MuFilterHit.h +++ b/shipLHC/MuFilterHit.h @@ -35,6 +35,7 @@ class MuFilterHit : public SndlhcHit Float_t GetDeltaT(Bool_t mask=kTRUE); Float_t GetFastDeltaT(Bool_t mask=kTRUE); Float_t GetImpactT(Bool_t mask=kTRUE); + Float_t GetImpactXpos(Bool_t mask=kTRUE, Bool_t isMC=kFALSE); bool isValid() const {return flag;} bool isMasked(Int_t i) const {return fMasked[i];} void SetMasked(Int_t i) {fMasked[i]=kTRUE;} From cda0fe6452f67a14f9f76c46adcc87bc6bad8397 Mon Sep 17 00:00:00 2001 From: siilieva Date: Wed, 19 Nov 2025 11:59:05 +0100 Subject: [PATCH 49/99] MuFilterHit: Use GetSystem() and GetPlane() in the class member functions --- shipLHC/MuFilterHit.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shipLHC/MuFilterHit.cxx b/shipLHC/MuFilterHit.cxx index a0c516354c..4ad2159802 100644 --- a/shipLHC/MuFilterHit.cxx +++ b/shipLHC/MuFilterHit.cxx @@ -138,8 +138,8 @@ Float_t MuFilterHit::GetEnergy() } bool MuFilterHit::isVertical(){ - if ( (floor(fDetectorID/10000)==3&&fDetectorID%1000>59) || - (floor(fDetectorID/10000)==1&&int(fDetectorID/1000)%10==2) ) { + if ( (GetSystem()==3&&fDetectorID%1000>59) || + (GetSystem()==1&&GetPlane()==2) ) { return kTRUE; } else{return kFALSE;} @@ -238,9 +238,9 @@ Float_t MuFilterHit::GetImpactT(Bool_t mask) Float_t dT = -999.; Float_t dL; MuFilter* MuFilterDet = dynamic_cast (gROOT->GetListOfGlobals()->FindObject("MuFilter")); - if (floor(fDetectorID/10000)==3) { + if (GetSystem()==3) { dL = MuFilterDet->GetConfParF("MuFilter/DownstreamBarX") / MuFilterDet->GetConfParF("MuFilter/DsPropSpeed");} - else if (floor(fDetectorID/10000)==2) { + else if (GetSystem()==2) { dL = MuFilterDet->GetConfParF("MuFilter/UpstreamBarX") / MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed");} else { dL = MuFilterDet->GetConfParF("MuFilter/VetoBarX") / MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed");} @@ -276,11 +276,11 @@ Float_t MuFilterHit::GetImpactXpos(Bool_t mask, Bool_t isMC) MuFilter* MuFilterDet = dynamic_cast (gROOT->GetListOfGlobals()->FindObject("MuFilter")); Float_t bar_length = MuFilterDet->GetConfParF("MuFilter/UpstreamBarX"); Float_t signal_speed = MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed"); - if (floor(fDetectorID/10000)==3) { + if (GetSystem()==3) { signal_speed = MuFilterDet->GetConfParF("MuFilter/DsPropSpeed"); bar_length = MuFilterDet->GetConfParF("MuFilter/DownstreamBarX"); } - else if (floor(fDetectorID/10000)==2) { + else if (GetSystem()==2) { bar_length = MuFilterDet->GetConfParF("MuFilter/UpstreamBarX"); } else { From c3763e673215f9f5af77fe14542a6ba84dff3f1b Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 18 Nov 2025 00:02:03 +0100 Subject: [PATCH 50/99] ScifiPlane: remove invalid hits in the plane constructor Also optimized the constructor to use detectorID straight up, spotted by Filippo. --- analysis/tools/sndScifiPlane.cxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/analysis/tools/sndScifiPlane.cxx b/analysis/tools/sndScifiPlane.cxx index ef1dfe0671..7bf9452c9a 100644 --- a/analysis/tools/sndScifiPlane.cxx +++ b/analysis/tools/sndScifiPlane.cxx @@ -16,14 +16,15 @@ snd::analysis_tools::ScifiPlane::ScifiPlane(std::vector snd_hits, { for ( auto snd_hit : snd_hits) { + if (!snd_hit->isValid()) continue; ScifiHit hit; hit.channel_index = 512 * snd_hit->GetMat() + 128 * snd_hit->GetSiPM() + snd_hit->GetSiPMChan(); - hit.timestamp = (scifi_geometry->GetCorrectedTime(snd_hit->GetDetectorID(), snd_hit->GetTime(0)*ShipUnit::snd_TDC2ns, 0) / ShipUnit::snd_TDC2ns); // timestamp is in clock cycles + int detectorID = snd_hit->GetDetectorID(); + hit.timestamp = (scifi_geometry->GetCorrectedTime(detectorID, snd_hit->GetTime(0)*ShipUnit::snd_TDC2ns, 0) / ShipUnit::snd_TDC2ns); // timestamp is in clock cycles hit.qdc = snd_hit->GetSignal(0); hit.is_x = snd_hit->isVertical(); TVector3 A, B; - int detectorID = snd_hit->GetDetectorID(); scifi_geometry->GetSiPMPosition(detectorID, A, B); hit.z = A.Z(); if (hit.is_x) From c62da50ae737cebe86329810bf2e16b7e3465a2f Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 18 Nov 2025 00:05:40 +0100 Subject: [PATCH 51/99] ShowerTools: add Scifi and US spatial anisotropy The functions take as arguments Scifi/US plane hits. There is an option to use plane centroids for the anisotropy estimation - to be tested if makes sense. It is up to the user to filter in time and/or find the centroids(if flag is set to 1) prior the call to anisotropy functions. This is done to remove redundancies and provide flexibility to the users. --- analysis/tools/AnalysisToolsLinkDef.h | 2 + analysis/tools/sndShowerTools.cxx | 131 ++++++++++++++++++++++++++ analysis/tools/sndShowerTools.h | 6 +- 3 files changed, 138 insertions(+), 1 deletion(-) diff --git a/analysis/tools/AnalysisToolsLinkDef.h b/analysis/tools/AnalysisToolsLinkDef.h index 07ae4f6fbc..0b2e56ead1 100644 --- a/analysis/tools/AnalysisToolsLinkDef.h +++ b/analysis/tools/AnalysisToolsLinkDef.h @@ -46,5 +46,7 @@ #pragma link C++ function snd::analysis_tools::GetUSShowerEnd(const std::vector &); #pragma link C++ function snd::analysis_tools::GetShowerInterceptAndDirection(const snd::Configuration &, const std::vector &, const std::vector &); #pragma link C++ function snd::analysis_tools::GetShoweringPlanes(const std::vector &, const std::vector &); +#pragma link C++ function snd::analysis_tools::GetSciFiSpatialAnisotropy(const std::vector &, bool); +#pragma link C++ function snd::analysis_tools::GetUSSpatialAnisotropy(const std::vector &, bool); #endif diff --git a/analysis/tools/sndShowerTools.cxx b/analysis/tools/sndShowerTools.cxx index 1daf5c5d02..97d9789075 100644 --- a/analysis/tools/sndShowerTools.cxx +++ b/analysis/tools/sndShowerTools.cxx @@ -8,6 +8,8 @@ #include "sndScifiPlane.h" #include "sndUSPlane.h" #include "TMatrixD.h" +#include "TMatrixDSym.h" +#include "TMatrixDSymEigen.h" #include "TVectorD.h" #include "TDecompChol.h" #include "Math/Vector3D.h" @@ -147,3 +149,132 @@ std::pair, std::vectorPCA(const std::vector& u, const std::vector& z) +{ + // Need at least 3 measurements + assert(u.size() == z.size() && u.size() >= 2); + const size_t n_samples = u.size(); + const int n_variables = 2; + + // Find the mean values + double mean_u = 0.0, mean_z = 0.0; + for (size_t i = 0; i < n_samples; ++i) + { + mean_u += u[i]; + mean_z += z[i]; + } + mean_u /= n_samples; + mean_z /= n_samples; + + // Find the covariance components + double cov_uu = 0.0, cov_zz = 0.0, cov_uz = 0.0; + for (size_t i = 0; i < n_samples; ++i) + { + double du = u[i] - mean_u; + double dz = z[i] - mean_z; + cov_uu += du * du; + cov_zz += dz * dz; + cov_uz += du * dz; + } + + const double denom = (n_samples > 1) ? (n_samples - 1.0) : 1.0; + cov_uu /= denom; + cov_zz /= denom; + cov_uz /= denom; + + // Fill the covariance matrix + TMatrixDSym cov_matrix(n_variables); + cov_matrix[0][0] = cov_uu; + cov_matrix[0][1] = cov_uz; + cov_matrix[1][0] = cov_uz; + cov_matrix[1][1] = cov_zz; + + // Find the eigenvalues and eigenvectors. For now we only need the former. + // Perhaps the eigenvectors could be used in the future, so left that part in comments. + TMatrixDSymEigen eig_decomp(cov_matrix); + TVectorD evals = eig_decomp.GetEigenValues(); + //TMatrixD evecs = eig_decomp.GetEigenVectors(); + + // Make sure the order is descending, swap if needed + if (evals[0] < evals[1]) + { + std::swap(evals[0], evals[1]); + //evecs[0] = eig_decomp.GetEigenVectors()[1]; + //evecs[1] = eig_decomp.GetEigenVectors()[0]; + } + + return {evals[0], evals[1]}; +} + +std::pair snd::analysis_tools::GetSciFiSpatialAnisotropy(const std::vector &scifi_planes, bool use_all_centroids) +{ + std::vector x, y, zx, zy; + for (auto &p : scifi_planes) { + // Add measurements per projection and in the respective coordinate vectors + if (use_all_centroids == false) { + for (auto &hit : p.GetHits()) { + if (hit.is_x) { + x.push_back(hit.x); + zx.push_back(hit.z); + } else { + y.push_back(hit.y); + zy.push_back(hit.z); + } + } + } else { + auto centroid = p.GetCentroid(); + // Check that centroid is valid + if (!std::isnan(centroid.Z())) { + if (!std::isnan(centroid.X())) { + x.push_back(centroid.X()); + zx.push_back(centroid.Z()); + } + if (!std::isnan(centroid.Y())) { + y.push_back(centroid.Y()); + zy.push_back(centroid.Z()); + } + } + } + } + + // Need at least 3 measurements + if (x.size() < 2 || y.size() < 2 || zx.size() < 2 || zy.size() < 2) { + return {1., 1.}; + } + double lambda1, lambda2; + std::tie(lambda1, lambda2) = PCA(x, zx); + double anisotropy_xz = (lambda1 > 0) ? 1.0 - lambda2 / lambda1 : 0.0; + auto pca_result_yz = PCA(y, zy); + std::tie(lambda1, lambda2) = PCA(y, zy); + double anisotropy_yz = (lambda1 > 0) ? 1.0 - lambda2 / lambda1 : 0.0; + return {anisotropy_xz, anisotropy_yz}; +} + +double snd::analysis_tools::GetUSSpatialAnisotropy(const std::vector &us_planes, bool use_all_centroids) +{ + std::vector y, zy; + for (auto &p : us_planes) { + // Add measurements per projection and in the respective coordinate vectors + if (use_all_centroids == false) { + for (auto &hit : p.GetHits()) { + y.push_back(hit.y); + zy.push_back(hit.z); + } + } else { + auto centroid = p.GetCentroid(); + // Check that centroid is valid + if (!(std::isnan(centroid.Z()) || std::isnan(centroid.Y()))) { + y.push_back(centroid.Y()); + zy.push_back(centroid.Z()); + } + } + } + + // Need at least 3 measurements + if (y.size() < 2 || zy.size() < 2) { + return 1.; + } + double lambda1, lambda2; + std::tie(lambda1, lambda2) = PCA(y, zy); + return (lambda1 > 0) ? 1.0 - lambda2 / lambda1 : 0.0; +} diff --git a/analysis/tools/sndShowerTools.h b/analysis/tools/sndShowerTools.h index bf1e64e34a..79faaeb3bc 100644 --- a/analysis/tools/sndShowerTools.h +++ b/analysis/tools/sndShowerTools.h @@ -23,7 +23,11 @@ namespace snd { std::pair GetShowerInterceptAndDirection(const Configuration &configuration, const std::vector &scifi_planes, const std::vector &us_planes); // Filters out non showering planes std::pair, std::vector> GetShoweringPlanes(const std::vector &scifi_planes, const std::vector &us_planes); + // Returns the SciFi spatial anisotropy(track-likeness) per projection + std::pair GetSciFiSpatialAnisotropy(const std::vector &scifi_planes, bool use_all_centroids = false); + // Returns the US spatial anisotropy + double GetUSSpatialAnisotropy(const std::vector &us_planes, bool use_all_centroids = false); } } -#endif \ No newline at end of file +#endif From f257a51a6f289ce1d24b91a79a0a03191d7c1b15 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 27 Jun 2025 14:21:54 +0200 Subject: [PATCH 52/99] Disable web-viewer when checking for geo overlaps adjusted from FairShip commit https://github.com/ShipSoft/FairShip/pull/740/commits/9c9c0f000dad344cb010692b3925e97b3ba7b851 following a proposed workaroud in https://github.com/root-project/root/issues/18881 --- macro/run_simScript.py | 1 + shipLHC/run_simSND.py | 1 + 2 files changed, 2 insertions(+) diff --git a/macro/run_simScript.py b/macro/run_simScript.py index 3b871a8859..2404279bf3 100755 --- a/macro/run_simScript.py +++ b/macro/run_simScript.py @@ -673,6 +673,7 @@ # checking for overlaps if checking4overlaps: + ROOT.gROOT.SetWebDisplay("off") # Workaround for https://github.com/root-project/root/issues/18881 fGeo = ROOT.gGeoManager fGeo.SetNmeshPoints(10000) fGeo.CheckOverlaps(0.1) # 1 micron takes 5minutes diff --git a/shipLHC/run_simSND.py b/shipLHC/run_simSND.py index a4e93e430e..60dbf1b580 100644 --- a/shipLHC/run_simSND.py +++ b/shipLHC/run_simSND.py @@ -436,6 +436,7 @@ def Exec(self,opt): # ------------------------------------------------------------------------ def checkOverlaps(removeScifi=False): + ROOT.gROOT.SetWebDisplay("off") # Workaround for https://github.com/root-project/root/issues/18881 sGeo = ROOT.gGeoManager if removeScifi: for n in range(1,6): From 0f7c12188f8f4700b921f16f37f66f664c8851af Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 27 Jun 2025 17:55:38 +0200 Subject: [PATCH 53/99] Use ConstructedAt as TClonesArray.__setitem__ pythonization was removed This addresses the ROOT change https://github.com/root-project/root/commit/0afffc6fc9cdd39b5a701a9aa1f6525aaa7342b1 and was first adapted in Fairship commit https://github.com/ShipSoft/FairShip/commit/b9309d05f8f7b6055ebde24d387c6a07160cae5b For SND@LHC the usage is a little different for the muonDIS part. --- python/SndlhcDigi.py | 15 ++++++++++----- python/SndlhcMuonReco.py | 3 ++- python/SndlhcTracking.py | 3 ++- shipLHC/muonDis.py | 7 +++++-- shipLHC/rawData/ConvRawData.py | 16 ++++++++++++---- shipLHC/rawData/convertRawData_muTestbeam.py | 11 ++++++++--- shipLHC/scripts/Monitor.py | 9 ++++++--- 7 files changed, 45 insertions(+), 19 deletions(-) diff --git a/python/SndlhcDigi.py b/python/SndlhcDigi.py index 55d83ac8cf..644b824432 100644 --- a/python/SndlhcDigi.py +++ b/python/SndlhcDigi.py @@ -103,11 +103,13 @@ def digitizeScifi(self): allWeights.push_back(p[1]) aHit = ROOT.sndScifiHit(detID,allPoints,allWeights) if self.digiScifi.GetSize() == index: self.digiScifi.Expand(index+100) - self.digiScifi[index]=aHit + aHit_TCA = self.digiScifi.ConstructedAt(index) + ROOT.std.swap(aHit, aHit_TCA) index+=1 for k in mcPoints[detID]: mcLinks.Add(detID,k, mcPoints[detID][k]/norm[detID]) - self.digiScifi2MCPoints[0]=mcLinks + mcLinks_TCA = self.digiScifi2MCPoints.ConstructedAt(0) + ROOT.std.swap(mcLinks, mcLinks_TCA) def digitizeMuFilter(self): hitContainer = {} @@ -134,11 +136,13 @@ def digitizeMuFilter(self): aHit = ROOT.MuFilterHit(detID,allPoints) if self.digiMuFilter.GetSize() == index: self.digiMuFilter.Expand(index+100) - self.digiMuFilter[index]=aHit + aHit_TCA = self.digiMuFilter.ConstructedAt(index) + ROOT.std.swap(aHit, aHit_TCA) index+=1 for k in mcPoints[detID]: mcLinks.Add(detID,k, mcPoints[detID][k]/norm[detID]) - self.digiMuFilter2MCPoints[0]=mcLinks + mcLinks_TCA = self.digiMuFilter2MCPoints.ConstructedAt(0) + ROOT.std.swap(mcLinks, mcLinks_TCA) def clusterScifi(self): index = 0 @@ -167,7 +171,8 @@ def clusterScifi(self): for aHit in tmp: hitlist.push_back( self.sTree.Digi_ScifiHits[hitDict[aHit]],) aCluster = ROOT.sndCluster(first,N,hitlist,self.scifiDet) if self.clusScifi.GetSize() == index: self.clusScifi.Expand(index+10) - self.clusScifi[index]=aCluster + aCluster_TCA = self.clusScifi.ConstructedAt(index) + ROOT.std.swap(aCluster, aCluster_TCA) index+=1 if c!=hitList[last]: ncl+=1 diff --git a/python/SndlhcMuonReco.py b/python/SndlhcMuonReco.py index c12de6491e..d38a8ca06d 100644 --- a/python/SndlhcMuonReco.py +++ b/python/SndlhcMuonReco.py @@ -946,7 +946,8 @@ def Exec(self, opt) : this_track.setRawMeasTimes(pointTimes) this_track.setTrackType(self.track_type) # Save the track in sndRecoTrack format - self.kalman_tracks[i_muon] = this_track + this_track_TCA = self.kalman_tracks.ConstructedAt(i_muon) + ROOT.std.swap(this_track, this_track_TCA) # Delete the Kalman track object theTrack.Delete() diff --git a/python/SndlhcTracking.py b/python/SndlhcTracking.py index ff92e8cb00..6476b00166 100644 --- a/python/SndlhcTracking.py +++ b/python/SndlhcTracking.py @@ -132,7 +132,8 @@ def ExecuteTask(self,option='ScifiDS'): this_track.setRawMeasTimes(pointTimes) this_track.setTrackType(rc.GetUniqueID()) # Store the track in sndRecoTrack format - self.fittedTracks[i_muon] = this_track + this_track_TCA = self.fittedTracks.ConstructedAt(i_muon) + ROOT.std.swap(this_track, this_track_TCA) def DStrack(self): diff --git a/shipLHC/muonDis.py b/shipLHC/muonDis.py index 25401882fe..e5b6e2517a 100755 --- a/shipLHC/muonDis.py +++ b/shipLHC/muonDis.py @@ -504,7 +504,9 @@ def makeMuDISEvents(withElossFunction=False,nucleon='p+'): for n in range(options.nMult): dPart.Clear() iMuon.Clear() - iMuon[0] = muPart + muPart_replica = ROOT.TParticle(muPart) + muPart_TCA = iMuon.ConstructedAt(0) + ROOT.std.swap(muPart_replica, muPart_TCA) myPythia.GenerateEvent() # remove all unnecessary stuff myPythia.Pyedit(2) @@ -518,7 +520,8 @@ def makeMuDISEvents(withElossFunction=False,nucleon='p+'): # copy to branch nPart = dPart.GetEntries() if dPart.GetSize() == nPart: dPart.Expand(nPart+10) - dPart[nPart] = part + part_TCA = dPart.ConstructedAt(nPart) + ROOT.std.swap(part, part_TCA) nMade+=1 if nMade%options.heartbeat==0: print('made so far ',options.run,' :',nMade,time.ctime()) dTree.Fill() diff --git a/shipLHC/rawData/ConvRawData.py b/shipLHC/rawData/ConvRawData.py index b01682e3bb..1f99177815 100644 --- a/shipLHC/rawData/ConvRawData.py +++ b/shipLHC/rawData/ConvRawData.py @@ -551,11 +551,15 @@ def executeEvent1(self,eventNumber): # copy hits to detector branches for sipmID in digiSciFiStore: if self.digiSciFi.GetSize() == indexSciFi: self.digiSciFi.Expand(indexSciFi+100) - self.digiSciFi[indexSciFi]=digiSciFiStore[sipmID] + aHit = digiSciFiStore[sipmID] + aHit_TCA = self.digiSciFi.ConstructedAt(indexSciFi) + ROOT.std.swap(aHit, aHit_TCA) indexSciFi+=1 for detID in digiMuFilterStore: if self.digiMuFilter.GetSize() == indexMuFilter: self.digiMuFilter.Expand(indexMuFilter+100) - self.digiMuFilter[indexMuFilter]=digiMuFilterStore[detID] + aHit = digiMuFilterStore[detID] + aHit_TCA = self.digiMuFilter.ConstructedAt(indexMuFilter) + ROOT.std.swap(aHit, aHit_TCA) indexMuFilter+=1 def executeEvent0(self,eventNumber): if self.options.FairTask_convRaw: @@ -682,11 +686,15 @@ def executeEvent0(self,eventNumber): # copy hits to detector branches for sipmID in digiSciFiStore: if self.digiSciFi.GetSize() == indexSciFi: self.digiSciFi.Expand(indexSciFi+100) - self.digiSciFi[indexSciFi]=digiSciFiStore[sipmID] + aHit = digiSciFiStore[sipmID] + aHit_TCA = self.digiSciFi.ConstructedAt(indexSciFi) + ROOT.std.swap(aHit, aHit_TCA) indexSciFi+=1 for detID in digiMuFilterStore: if self.digiMuFilter.GetSize() == indexMuFilter: self.digiMuFilter.Expand(indexMuFilter+100) - self.digiMuFilter[indexMuFilter]=digiMuFilterStore[detID] + aHit = digiMuFilterStore[detID] + aHit_TCA = self.digiMuFilter.ConstructedAt(indexMuFilter) + ROOT.std.swap(aHit, aHit_TCA) indexMuFilter+=1 diff --git a/shipLHC/rawData/convertRawData_muTestbeam.py b/shipLHC/rawData/convertRawData_muTestbeam.py index 857c72c083..d35ea383af 100644 --- a/shipLHC/rawData/convertRawData_muTestbeam.py +++ b/shipLHC/rawData/convertRawData_muTestbeam.py @@ -471,11 +471,15 @@ def run(nEvent): for sipmID in digiSciFiStore: if digiSciFi.GetSize() == indexSciFi: digiSciFi.Expand(indexSciFi+100) - digiSciFi[indexSciFi]=digiSciFiStore[sipmID] + aHit = digiSciFiStore[sipmID] + aHit_TCA = digiSciFi.ConstructedAt(indexSciFi) + ROOT.std.swap(aHit, aHit_TCA) indexSciFi+=1 for detID in digiMuFilterStore: if digiMuFilter.GetSize() == indexMuFilter: digiMuFilter.Expand(indexMuFilter+100) - digiMuFilter[indexMuFilter]=digiMuFilterStore[detID] + aHit = digiMuFilterStore[detID] + aHit_TCA = digiMuFilter.ConstructedAt(indexMuFilter) + ROOT.std.swap(aHit, aHit_TCA) indexMuFilter+=1 # make simple clustering for scifi, only works with geometry file. Don't think it is a good idea at the moment if withGeoFile: @@ -509,7 +513,8 @@ def run(nEvent): aCluster = ROOT.sndCluster(first,N,hitlist,scifiDet) print("cluster created") if clusScifi.GetSize() == index: clusScifi.Expand(index+10) - clusScifi[index]=aCluster + aCluster_TCA = clusScifi.ConstructedAt(index) + ROOT.std.swap(aCluster, aCluster_TCA) index+=1 if c!=hitList[last]: ncl+=1 diff --git a/shipLHC/scripts/Monitor.py b/shipLHC/scripts/Monitor.py index 9bf7b09c79..a0b5ae61c9 100644 --- a/shipLHC/scripts/Monitor.py +++ b/shipLHC/scripts/Monitor.py @@ -848,7 +848,8 @@ def ExecuteEvent(self,event): for item in track_container_list: for aTrack in item: i_muon += 1 - self.fittedTracks[i_muon] = aTrack + aTrack_TCA = self.fittedTracks.ConstructedAt(i_muon) + ROOT.std.swap(aTrack, aTrack_TCA) def Execute(self): for n in range(self.options.nStart,self.options.nStart+self.options.nEvents): @@ -867,13 +868,15 @@ def Execute(self): self.clusScifi.Delete() self.clusScifi.Expand(len(self.trackTask.clusScifi)) for index, aCl in enumerate(self.trackTask.clusScifi): - self.clusScifi[index] = aCl + aCl_TCA = self.clusScifi.ConstructedAt(index) + ROOT.std.swap(aCl, aCl_TCA) if self.options.simpleTracking and not self.options.trackType.find('DS')<0: if not self.eventTree.GetBranch("Cluster_Mufi"): self.clusMufi.Delete() self.clusMufi.Expand(len(self.trackTask.clusMufi)) for index, aCl in enumerate(self.trackTask.clusMufi): - self.clusMufi[index] = aCl + aCl_TCA = self.clusMufi.ConstructedAt(index) + ROOT.std.swap(aCl, aCl_TCA) # if using FairEventHeader, i.e. before sndlhc header was introduced if hasattr(self.OT.EventHeader, "SetMCEntryNumber"): From a52e17409e860d816ff1c4261c5323b6066e57ef Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 27 Jun 2025 18:14:23 +0200 Subject: [PATCH 54/99] Use the pyExit digi hack only when running with the cpp DigiTask Otherwise, using py digi, the hit information is not stored in the output tree. --- shipLHC/run_digiSND.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shipLHC/run_digiSND.py b/shipLHC/run_digiSND.py index 776714dd45..4dd1432be3 100644 --- a/shipLHC/run_digiSND.py +++ b/shipLHC/run_digiSND.py @@ -7,7 +7,6 @@ def pyExit(): # This is needed to bypass seg violation with exiting cpp digitization # Most likely related to file ownership. os.system('kill '+str(os.getpid())) -atexit.register(pyExit) import resource def mem_monitor(): @@ -148,3 +147,5 @@ def mem_monitor(): ctime = timer.CpuTime() print(' ') print("Real time ",rtime, " s, CPU time ",ctime,"s") +if options.FairTask_digi: + atexit.register(pyExit) From 863e430322b2fa557572eb669e5ae27b67b9e51e Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 27 Jun 2025 18:14:32 +0200 Subject: [PATCH 55/99] Account for removal of attribute pythonization of TDirectory Syntax of type my_file.my_tree is removed since ROOT 6.34. To adapt, but also stay backward compatible!, use my_file.Get("my_tree"). A more pythonic solution was introduced in 6.32, namely my_file["my_tree"], but it won't work with earlier ROOT versions. --- macro/MufluxReco.py | 2 +- macro/run_simScript.py | 2 +- python/SndlhcDigi.py | 2 +- python/rootUtils.py | 4 +- shipLHC/ana_thermalNeutrons.py | 14 +++--- shipLHC/muonDis.py | 16 +++---- shipLHC/rawData/ConvRawData.py | 14 +++--- shipLHC/rawData/convertRawData_muTestbeam.py | 10 ++-- shipLHC/rawData/mufiHitMaps.py | 4 +- shipLHC/rawData/runProd.py | 12 ++--- shipLHC/rawData/scifiHitMaps.py | 4 +- shipLHC/run_muonRecoSND.py | 2 +- shipLHC/scripts/2dEventDisplay.py | 4 +- shipLHC/scripts/2dMuEventBuilderDisplay.py | 6 +-- shipLHC/scripts/FillingScheme.py | 48 ++++++++++---------- shipLHC/scripts/Monitor.py | 4 +- shipLHC/scripts/MufiCTR.py | 4 +- shipLHC/scripts/Survey-MufiScifi.py | 4 +- shipLHC/thermalNeutrons.py | 2 +- 19 files changed, 79 insertions(+), 79 deletions(-) diff --git a/macro/MufluxReco.py b/macro/MufluxReco.py index eea4ab39ff..18ea26f370 100644 --- a/macro/MufluxReco.py +++ b/macro/MufluxReco.py @@ -97,7 +97,7 @@ def mem_monitor(): # check if simulation or raw data f=ROOT.TFile.Open(outFile) -if f.cbmsim.FindBranch('MCTrack'): simulation = True +if f.Get("cbmsim").FindBranch('MCTrack'): simulation = True else: simulation = False f.Close() diff --git a/macro/run_simScript.py b/macro/run_simScript.py index 2404279bf3..79cf1f82d7 100755 --- a/macro/run_simScript.py +++ b/macro/run_simScript.py @@ -708,7 +708,7 @@ nm = ff.GetName().split('/') if nm[len(nm)-1] == check: fin = ff if not fin: fin = ROOT.TFile.Open(outFile) - t = fin.cbmsim + t = fin.Get("cbmsim") fout = ROOT.TFile(tmpFile,'recreate') sTree = t.CloneTree(0) nEvents = 0 diff --git a/python/SndlhcDigi.py b/python/SndlhcDigi.py index 644b824432..5900982843 100644 --- a/python/SndlhcDigi.py +++ b/python/SndlhcDigi.py @@ -16,7 +16,7 @@ def __init__(self,fout, makeClusterScifi): outdir=os.getcwd() outfile=outdir+"/"+fout self.fn = ROOT.TFile(fout,'update') - self.sTree = self.fn.cbmsim + self.sTree = self.fn.Get("cbmsim") # event header self.header = ROOT.SNDLHCEventHeader() diff --git a/python/rootUtils.py b/python/rootUtils.py index 2b962fee96..05829a0040 100644 --- a/python/rootUtils.py +++ b/python/rootUtils.py @@ -146,7 +146,7 @@ def container_sizes(sTree,perEvent=False): def stripOffBranches(fout): f = TFile(fout) - sTree = f.cbmsim + sTree = f.Get("cbmsim") nEvents = sTree.GetEntries() strip = False oldTargetClass = False @@ -178,7 +178,7 @@ def stripOffBranches(fout): recf.Close() # should do some sanity checks before deleting old file f = TFile(sFile) - sTree = f.cbmsim + sTree = f.Get("cbmsim") if nEvents == sTree.GetEntries(): print("looks ok, could be deleted",os.path.abspath('.')) else: print("stripping failed, keep old file",os.path.abspath('.')) # os.system('mv '+sFile +' '+fout) diff --git a/shipLHC/ana_thermalNeutrons.py b/shipLHC/ana_thermalNeutrons.py index f48fd7203a..181dd2a181 100644 --- a/shipLHC/ana_thermalNeutrons.py +++ b/shipLHC/ana_thermalNeutrons.py @@ -30,7 +30,7 @@ def count(hFile): ut.bookHist(h,'xyz','', 100,-0.1,0.1,100,-0.1,0.1,200,-1.,1.) ut.bookHist(h,'dxyz','',100,-0.1,0.1,100,-0.1,0.1,200,-1.,1.) Npassed = 0 - for sTree in f.cbmsim: + for sTree in f.Get("cbmsim"): N = sTree.MCTrack[0] Ekin = N.GetP()**2/(2*N.GetMass())*1000. logEkin = ROOT.TMath.Log10(Ekin) @@ -152,7 +152,7 @@ def absorptionLength(plotOnly=True): myPrint(h['T'+X],'fracEveWith'+X) # fntuple = ROOT.TFile.Open('neutronsTI18.root') - nt=fntuple.nt + nt=fntuple.Get("nt") ROOT.gROOT.cd() tcanv = 'TFig12' if tcanv in h: h.pop(tcanv) @@ -201,7 +201,7 @@ def absorptionLength(plotOnly=True): n = 0 RateIntegrated = 0 RateIntegratedW = 0 - for nt in fntuple.nt: + for nt in fntuple.Get("nt"): E = (nt.Eleft+nt.Eright)/2. dE = nt.Eright - nt.Eleft h['Fig12'].SetPoint(n,ROOT.TMath.Log10(E),ROOT.TMath.Log10(nt.N*E)) @@ -312,7 +312,7 @@ def reactions(hFile,distance=1E10): ut.bookHist(h,'E',';log10(Ekin [MeV])',1000,-12.,4.) stats = {} n=-1 - for sTree in f.cbmsim: + for sTree in f.Get("cbmsim"): n+=1 daughters = [] for m in sTree.MCTrack: @@ -397,7 +397,7 @@ def flukaRateIntegrated(save=False): h['neutronRate'] = ROOT.TGraph() h['N'] = ROOT.TGraph() n = 0 - for nt in fntuple.nt: + for nt in fntuple.Get("nt"): # nt.N = [cm/MeV] E = (nt.Eleft+nt.Eright)/2. dE = nt.Eright - nt.Eleft @@ -516,8 +516,8 @@ def coldBox(plotOnly=True,pas=''): ut.bookHist(h,'multN','mult neutrons',100,-0.5,99.5) flukaRateIntegrated() - Nsim = f.cbmsim.GetEntries() - for sTree in f.cbmsim: + Nsim = f.Get("cbmsim").GetEntries() + for sTree in f.Get("cbmsim"): rc=h['multN'].Fill(sTree.MCTrack.GetEntries()) neutron = sTree.MCTrack[0] start = ROOT.TVector3(neutron.GetStartX(),neutron.GetStartY(),neutron.GetStartZ()) diff --git a/shipLHC/muonDis.py b/shipLHC/muonDis.py index e5b6e2517a..1e8f2ce829 100755 --- a/shipLHC/muonDis.py +++ b/shipLHC/muonDis.py @@ -181,7 +181,7 @@ def convertAscii2Root(fname,version=2): def muonPreTransport(): f = ROOT.TFile(options.muonIn) - nt = f.nt + nt = f.Get("nt") foutName = options.muonIn.replace('.root','_z'+str(options.z)+'.root') fout = ROOT.TFile(foutName,'recreate') variables = "" @@ -471,7 +471,7 @@ def makeMuDISEvents(withElossFunction=False,nucleon='p+'): dPartBranch = dTree.Branch("Particles",dPart,32000,-1) # read file with muons hitting concrete wall fin = ROOT.TFile(options.muonIn) # id:px:py:pz:x:y:z:w - sTree = fin.nt + sTree = fin.Get("nt") nTOT = sTree.GetEntries() nEnd = min(nTOT,options.nStart + options.nEvents) # stop pythia printout during loop @@ -835,7 +835,7 @@ def muonRateAtSND(withFaser=False,withEff=False,version=1): ut.bookHist(h,'tanThetaXYMS_'+str(mu), 'tan theta X/Y',200,-0.01,0.01,200,-0.01,0.01) ut.bookHist(h,'tanThetaXYMSfaser_'+str(mu), 'tan theta X/Y',200,-0.01,0.01,200,-0.01,0.01) ut.bookHist(h,'theta', 'mult scattering angle',200,-1.,1.) - for sTree in fin.nt: + for sTree in fin.Get("nt"): px,py,pz = sTree.px,sTree.py,sTree.pz m2cm = 1. if version == 0: m2cm = 100 @@ -1027,7 +1027,7 @@ def flukaMuons(version=1,Plimit=False,withFaser=True): ut.bookHist(h,'W_'+str(mu), 'w',100,0.,0.15) for mu in fnames: fin = ROOT.TFile(fnames[mu]) - for sTree in fin.nt: + for sTree in fin.Get("nt"): m2cm = 1. if version == 0: m2cm = 100. P = ROOT.TVector3(sTree.px,sTree.py,sTree.pz) @@ -1149,7 +1149,7 @@ def muInterGeant4(version=2,njobs=100): f = ROOT.TFile(fname) ROOT.gROOT.cd() nEv = -1 - for sTree in f.cbmsim: + for sTree in f.Get("cbmsim"): nEv+=1 muon = sTree.MCTrack[0] w = norm*muon.GetWeight() @@ -1217,7 +1217,7 @@ def muondEdX(version=2,njobs=100,path='',withFaser=False, plotOnly=True): h[fname] = ROOT.TFile(fname) ROOT.gROOT.cd() nEv = -1 - for sTree in h[fname].cbmsim: + for sTree in h[fname].Get("cbmsim"): nEv+=1 muon = sTree.MCTrack[0] w = norm*muon.GetWeight() @@ -1647,7 +1647,7 @@ def muonDISfull(cycle = 0, sMin=0,sMax=200,rMin=1,rMax=11,path = '/eos/experimen if path.find('eos')<0: h['f'] = ROOT.TFile.Open(path+prod+datafile) else: h['f'] = ROOT.TFile.Open(os.environ['EOSSHIP']+path+prod+datafile) nEv = -1 - for sTree in h['f'].cbmsim: + for sTree in h['f'].Get("cbmsim"): nEv+=1 if nEv%1000 == 0: print('N ',nEv,prod) muon = sTree.MCTrack[0] @@ -1790,7 +1790,7 @@ def thermNeutron(): ROOT.gROOT.cd() for pid in ['13','-13']: h['g_'+pid] = fin.Get('g_'+pid).Clone('g_'+pid) - for sTree in f.cbmsim: + for sTree in f.Get("cbmsim"): muon = sTree.MCTrack[0] muonEnergy = muon.GetEnergy() mupid = muon.GetPdgCode() diff --git a/shipLHC/rawData/ConvRawData.py b/shipLHC/rawData/ConvRawData.py index 1f99177815..83aa283ba4 100644 --- a/shipLHC/rawData/ConvRawData.py +++ b/shipLHC/rawData/ConvRawData.py @@ -93,11 +93,11 @@ def Init(self,options): if self.fiN.Get('event'): self.newFormat = False # old format if not self.monitoring: if self.newFormat: - if options.nEvents<0: self.nEvents = self.fiN.data.GetEntries() - else: self.nEvents = min(options.nEvents,self.fiN.data.GetEntries()) + if options.nEvents<0: self.nEvents = self.fiN.Get("data").GetEntries() + else: self.nEvents = min(options.nEvents,self.fiN.Get("data").GetEntries()) else: - if options.nEvents<0: self.nEvents = self.fiN.event.GetEntries() - else: self.nEvents = min(options.nEvents,self.fiN.event.GetEntries()) + if options.nEvents<0: self.nEvents = self.fiN.Get("event").GetEntries() + else: self.nEvents = min(options.nEvents,self.fiN.Get("event").GetEntries()) print('converting ',self.nEvents,' events ',' of run',options.runNumber) # Pass input parameters to the task - runN, paths, etc. ioman.RegisterInputObject('runN', ROOT.TObjString(str(options.runNumber))) @@ -429,7 +429,7 @@ def executeEvent1(self,eventNumber): if eventNumber%self.options.heartBeat==0 or self.debug: print('run ',self.options.runNumber, ' event',eventNumber," ",time.ctime()) - event = self.fiN.data + event = self.fiN.Get("data") event.GetEvent(eventNumber) self.header.SetEventTime(event.evt_timestamp) self.header.SetUTCtimestamp(int(event.evt_timestamp*6.23768*1e-9 + self.run_startUTC)) @@ -572,7 +572,7 @@ def executeEvent0(self,eventNumber): return if eventNumber%self.options.heartBeat==0 or self.debug: print('run ',self.options.runNumber, ' event',eventNumber," ",time.ctime()) - event = self.fiN.event + event = self.fiN.Get("event") rc = event.GetEvent(eventNumber) self.header.SetEventTime(event.timestamp) self.header.SetRunId( self.options.runNumber ) @@ -709,7 +709,7 @@ def Finalize(self): self.outfile.Close() else: if self.options.debug: - print('number of events processed',self.sTree.GetEntries(),self.fiN.event.GetEntries()) + print('number of events processed',self.sTree.GetEntries(),self.fiN.Get("event").GetEntries()) self.sTree.Write() self.fiN.Close() self.fSink.Close() diff --git a/shipLHC/rawData/convertRawData_muTestbeam.py b/shipLHC/rawData/convertRawData_muTestbeam.py index d35ea383af..9e6a20cd7f 100644 --- a/shipLHC/rawData/convertRawData_muTestbeam.py +++ b/shipLHC/rawData/convertRawData_muTestbeam.py @@ -269,8 +269,8 @@ def channel(tofpet_id,tofpet_channel,position): if not (options.partition < 0): part = '_'+str(options.partition).zfill(4) dataName = 'data'+part+'.root' f0=ROOT.TFile.Open(X+path+dataName) -if options.nEvents<0: nEvent = f0.event.GetEntries() -else: nEvent = min(options.nEvents,f0.event.GetEntries()) +if options.nEvents<0: nEvent = f0.Get("event").GetEntries() +else: nEvent = min(options.nEvents,f0.Get("event").GetEntries()) print('converting ',nEvent,' events ',' of run',options.runNumber) boards = {} @@ -303,7 +303,7 @@ def channel(tofpet_id,tofpet_channel,position): import time def run(nEvent): - event = f0.event + event = f0.Get("event") for eventNumber in range(options.nStart,nEvent): ncreated = 0 rc = event.GetEvent(eventNumber) @@ -525,7 +525,7 @@ def run(nEvent): if options.debug: print('====> number of created hits',ncreated) if options.debug: - print('number of events processed',sTree.GetEntries(),f0.event.GetEntries()) + print('number of events processed',sTree.GetEntries(),f0.Get("event").GetEntries()) sTree.Write() # https://gitlab.cern.ch/snd-scifi/software/-/wikis/Raw-data-format @@ -538,7 +538,7 @@ def run(nEvent): theMap = {} def getMapEvent2Time(): k = 0 - for event in f0.event: + for event in f0.Get("event"): theMap[event.timestamp]=k k+=1 if k%10000000==0: print('key time map: now at ',k) diff --git a/shipLHC/rawData/mufiHitMaps.py b/shipLHC/rawData/mufiHitMaps.py index 52df158965..7b3700ed73 100644 --- a/shipLHC/rawData/mufiHitMaps.py +++ b/shipLHC/rawData/mufiHitMaps.py @@ -45,10 +45,10 @@ def smallSiPMchannel(i): if options.runNumber>0: f=ROOT.TFile.Open(path+'sndsw_raw_'+str(options.runNumber).zfill(6)+'.root') - eventTree = f.rawConv + eventTree = f.Get("rawConv") else: f=ROOT.TFile.Open(options.fname) - eventTree = f.cbmsim + eventTree = f.Get("cbmsim") # backward compatbility for early converted events eventTree.GetEvent(0) diff --git a/shipLHC/rawData/runProd.py b/shipLHC/rawData/runProd.py index d826235a1e..aeb037fdc2 100644 --- a/shipLHC/rawData/runProd.py +++ b/shipLHC/rawData/runProd.py @@ -209,15 +209,15 @@ def checkFile(self,path,r,p): print('checkfile',path,r,p) inFile = self.options.server+path+'run_'+ r+'/data_'+p+'.root' fI = ROOT.TFile.Open(inFile) - if fI.Get('event'): Nraw = fI.event.GetEntries() - else: Nraw = fI.data.GetEntries() + if fI.Get('event'): Nraw = fI.Get("event").GetEntries() + else: Nraw = fI.Get("data").GetEntries() outFile = 'sndsw_raw_'+r+'-'+p+self.Mext+'.root' fC = ROOT.TFile(outFile) test = fC.Get('rawConv') if not test: print('Error:',path,r,p,' rawConv not found') return -2 - Nconv = fC.rawConv.GetEntries() + Nconv = fC.Get("rawConv").GetEntries() if Nraw != Nconv: print('Error:',path,r,p,':',Nraw,Nconv) return -1 @@ -331,7 +331,7 @@ def getConvStats(self,runList): for run in runList: try: f=ROOT.TFile.Open("sndsw_raw_"+str(run).zfill(6)+'.root') - print(run,':',f.rawConv.GetEntries()) + print(run,':',f.Get("rawConv").GetEntries()) except: print('problem:',run) @@ -339,8 +339,8 @@ def rawStats(self,runList): for run in runList: runNr = str(run).zfill(6) r = ROOT.TFile.Open(os.environ['EOSSHIP']+path+"/run_"+runNr+"/data.root") - if fI.Get('event'): raw = r.event.GetEntries() - else: raw = r.data.GetEntries() + if fI.Get('event'): raw = r.Get("event").GetEntries() + else: raw = r.Get("data").GetEntries() print(run,':',raw) def makeHistos(self,runList): diff --git a/shipLHC/rawData/scifiHitMaps.py b/shipLHC/rawData/scifiHitMaps.py index 61a875ca44..fbb510af2a 100644 --- a/shipLHC/rawData/scifiHitMaps.py +++ b/shipLHC/rawData/scifiHitMaps.py @@ -32,10 +32,10 @@ def xPos(detID): if options.runNumber>0: f=ROOT.TFile.Open(options.path+'sndsw_raw_'+str(options.runNumber).zfill(6)+'.root') - eventTree = f.rawConv + eventTree = f.Get("rawConv") else: f=ROOT.TFile.Open(options.fname) - eventTree = f.cbmsim + eventTree = f.Get("cbmsim") def slopes(Nev=-1): A,B = ROOT.TVector3(),ROOT.TVector3() diff --git a/shipLHC/run_muonRecoSND.py b/shipLHC/run_muonRecoSND.py index 9692868a60..c3a990f265 100644 --- a/shipLHC/run_muonRecoSND.py +++ b/shipLHC/run_muonRecoSND.py @@ -75,7 +75,7 @@ treename = None for test_treename in ["rawConv", "cbmsim"] : - if hasattr(F, test_treename) : + if F.Get(test_treename): treename = test_treename break diff --git a/shipLHC/scripts/2dEventDisplay.py b/shipLHC/scripts/2dEventDisplay.py index 0aa16f97a2..1b5d9fa1e3 100644 --- a/shipLHC/scripts/2dEventDisplay.py +++ b/shipLHC/scripts/2dEventDisplay.py @@ -108,11 +108,11 @@ def pyExit(): f=ROOT.TFile.Open(options.path+options.inputFile) if f.FindKey('cbmsim'): - eventTree = f.cbmsim + eventTree = f.Get("cbmsim") runId = 'sim' if eventTree.GetBranch('ScifiPoint'): mc = True else: - eventTree = f.rawConv + eventTree = f.Get("rawConv") ioman.SetTreeName('rawConv') outFile = ROOT.TMemFile('dummy','CREATE') diff --git a/shipLHC/scripts/2dMuEventBuilderDisplay.py b/shipLHC/scripts/2dMuEventBuilderDisplay.py index eac251b884..47372acfc3 100644 --- a/shipLHC/scripts/2dMuEventBuilderDisplay.py +++ b/shipLHC/scripts/2dMuEventBuilderDisplay.py @@ -33,13 +33,13 @@ mc = False if options.inputFile=="": f=ROOT.TFile('sndsw_raw_'+str(options.runNumber).zfill(6)+'.root') - eventTree = f.rawConv + eventTree = f.Get("rawConv") else: f=ROOT.TFile.Open(options.inputFile) if f.FindKey('cbmsim'): - eventTree = f.cbmsim + eventTree = f.Get("cbmsim") mc = True - else: eventTree = f.rawConv + else: eventTree = f.Get("rawConv") nav = ROOT.gGeoManager.GetCurrentNavigator() for p in [0,1]: diff --git a/shipLHC/scripts/FillingScheme.py b/shipLHC/scripts/FillingScheme.py index b43b0e3281..8c7e30037b 100644 --- a/shipLHC/scripts/FillingScheme.py +++ b/shipLHC/scripts/FillingScheme.py @@ -159,10 +159,10 @@ def getFillNrFromRunNr(self,runNumber): options.rawData+"/run_"+str(runNumber).zfill(6)+"/data_0000.root") try: if R.Get('event'): - rc = R.event.GetEvent(R.event.GetEntries()-1) - flags = R.event.flags + rc = R.Get("event").GetEvent(R.Get("event").GetEntries()-1) + flags = R.Get("event").flags else: - event = R.data + event = R.Get("data") event.GetEvent(0) flags = event.evt_flags fillNumber = numpy.bitwise_and(FILL_NUMBER_MASK,flags) @@ -259,7 +259,7 @@ def getLumiAtIP1(self,fillnr=None,fromnxcals=False, fromAtlas=False): def drawLumi(self,runNumber): R = ROOT.TFile.Open(www+"offline/run"+str(runNumber).zfill(6)+".root") ROOT.gROOT.cd() - bCanvas = R.daq.Get('T') + bCanvas = R.Get("daq").Get('T') Xt = {'time':None,'timeWtDS':None,'timeWt':None} for x in Xt: Xt[x] = bCanvas.FindObject(x) @@ -582,9 +582,9 @@ def extractPhaseShift(self,fillNr,runNumber): R = ROOT.TFile.Open(www+"offline/run"+str(runNumber).zfill(6)+".root") ROOT.gROOT.cd() try: - self.h['bnr'] = R.daq.Get('bunchNumber').FindObject('bnr').Clone('bnr') + self.h['bnr'] = R.Get("daq").Get('bunchNumber').FindObject('bnr').Clone('bnr') except: - self.h['bnr'] = R.daq.Get('shifter/bunchNumber').FindObject('bnr').Clone('bnr') + self.h['bnr'] = R.Get("daq").Get('shifter/bunchNumber').FindObject('bnr').Clone('bnr') R.Close() # create the bunch number plot if offline monitoring file is missing except: @@ -684,9 +684,9 @@ def plotBunchStructure(self,fillNr,runNumber): try: R = ROOT.TFile.Open(www+"offline/run"+str(runNumber).zfill(6)+".root") ROOT.gROOT.cd() - bCanvas = R.daq.Get('bunchNumber') + bCanvas = R.Get("daq").Get('bunchNumber') if not bCanvas: - bCanvas = R.daq.shifter.Get('bunchNumber') + bCanvas = R.Get("daq").Get("shifter").Get('bunchNumber') h['bnr']= bCanvas.FindObject('bnr').Clone('bnr') # create the bunch number plot if offline monitoring file is missing except: @@ -765,7 +765,7 @@ def Xbunch(self): h['XIP2z'].SetStats(0) R = ROOT.TFile.Open(www+"offline/run"+str(runNumber).zfill(6)+".root") ROOT.gROOT.cd() - bCanvas = R.daq.Get('sndclock') + bCanvas = R.Get("daq").Get('sndclock') h['Xbnr']= bCanvas.FindObject('Xbnr').Clone('Xbnr') ROOT.gROOT.cd() h['c1'].cd() @@ -980,7 +980,7 @@ def FwBw(self,runNumber): xing = {'all':False,'B1only':False,'B2noB1':False,'noBeam':False} for T in ['Txing','TD','T']: - h[T] = self.F.daq.Get(T).Clone(T) + h[T] = self.F.Get("daq").Get(T).Clone(T) for X in ['time','timeWt','timeWtDS']: h[X] = h['T'].FindObject(X).Clone(X) for t in ['time','timeWt','timeWtDS','bnr']: @@ -1158,9 +1158,9 @@ def FwBw(self,runNumber): for B in ['B2noB1','B1only','noBeam']: for x in ['scifi-trackDir','scifi-TtrackPos']: T = x+B - tmp = self.F.scifi.Get(T) + tmp = self.F.Get("scifi").Get(T) if not tmp: - tmp = self.F.scifi.Get(B+'/'+T) + tmp = self.F.Get("scifi").Get(B+'/'+T) h[T] = tmp.Clone(T) if x.find('Dir')>0: for y in ['scifi-trackSlopesXL','slopeXL','slopeYL']: @@ -1189,9 +1189,9 @@ def FwBw(self,runNumber): for x in ['muonDSTracks','mufi-TtrackPos']: T = x+B - tmp = self.F.mufilter.Get(T) + tmp = self.F.Get("mufilter").Get(T) if not tmp: - tmp = self.F.mufilter.Get(B+'/'+T) + tmp = self.F.Get("mufilter").Get(B+'/'+T) h[T] = tmp.Clone(T) if x.find('DSTrack')>0: for y in ['mufi-slopes','slopeX','slopeY']: @@ -1643,7 +1643,7 @@ def runsWithBeam(self): runNumber = int(x[ir+4:ir+4+k+1]) if runNumber Date: Wed, 8 Oct 2025 12:32:01 +0200 Subject: [PATCH 56/99] add to CHANGELOG the adjustments for 2025 Oct7 stack --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d939e61bc..3acea5a7f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,12 @@ We start with the first sndsw release: v1.0.0+2025-07-updateScifi. Shall there be a strong will/need, one can go back and create and fill in the logs for previous stacks. + +### Fixed + +- multiple fixes due to drop of attribute pythonization for TDirectory and TClonesArray +- Disable web-viewer when checking for geo overlaps (workaround!) + ## v1.2.1+2025-09 ### Added From 9501622c17d905b5081afc935fb092c3c0b5d440 Mon Sep 17 00:00:00 2001 From: siilieva <84918585+siilieva@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:24:07 +0100 Subject: [PATCH 57/99] G4conf: Remove neutron and hadronic process verbosity settings /mcPhysics/g4NeutronHPVerbose 0 breaks code execution with GEANT4 v11.4.beta. The impact of the two removed verbosity settings on the log using the previous v11.1.0 is none. The original commit, which includes both settings, dates 11 years back. In the meantime, NeutronHP merged to ParticleHP and seems support for pure NeutronHP is (perhaps gradually) being dropped. --- gconfig/g4config.in | 2 -- 1 file changed, 2 deletions(-) diff --git a/gconfig/g4config.in b/gconfig/g4config.in index de79f5aeea..e61e8d4207 100644 --- a/gconfig/g4config.in +++ b/gconfig/g4config.in @@ -4,8 +4,6 @@ /mcVerbose/all 0 /mcTracking/loopVerbose 0 # suggested by Ivana Hrivnacova to switch off *** Particle reached max step number .... -/mcPhysics/g4NeutronHPVerbose 0 # - suppress the warnings about missing neutron data -/mcPhysics/g4HadronicProcessStoreVerbose 0 # switch on other sources of di muon /physics_lists/em/PositronToMuons true From 8d0aa0d826bc71641f2d28d514032e9f13a92916 Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 1 Dec 2025 14:43:52 +0100 Subject: [PATCH 58/99] prepare CHANGELOG for new v1.3.0+2025-11 release --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3acea5a7f0..4865f8c449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,9 +26,37 @@ We start with the first sndsw release: v1.0.0+2025-07-updateScifi. Shall there be a strong will/need, one can go back and create and fill in the logs for previous stacks. +## v1.3.0+2025-11-showerToolsAndP8Decayer + +### Added + +- shower tools: start, stop, shower direction +- 2dED: Add option to plot shower direction +- python script to make run lists with DB info +- SciFi and US anisotropy tools +- USPlane: GetNHitBars() returting N bars with fired SiPMs above threshold +- MuFilterHit: Add GetImpactXpos method +- add time window selection for US(test_beam_2023 configuration) +- alignment of targets 256-258 + +### Changed + +- G4conf: Remove obsolete neutron and hadronic process verbosity settings +- improve centroid error: use propagation of uncertainty respecting the weight(=qdc) per hit. +- USPlane: Utilize left/right measurements to determine X position along bar +- USPlane: use only small SiPMs to determine if plane HasShower() +- set default csv tables for geofiles and data ROOT files + - these changes in are not backward-compatible, but only affect analysis code that is not part of sndsw +- remove invalid SciFi hits from ScifiPlane's constructor +- 2dED: Plot tracks starting from most upstream Veto ### Fixed +- change pythia8 decayer configuration method + - heavy mesons and taus are decay’ed using external Pythia8 decayer +- USPlane: Fix FindCentroid method +- Fix reference SciFi time intervals to values used in prev analysis +- MuFilterHit: fix GetImpactT method - multiple fixes due to drop of attribute pythonization for TDirectory and TClonesArray - Disable web-viewer when checking for geo overlaps (workaround!) From 0cc213bbf5d1fb74fca3d252a1b75277abb8e90a Mon Sep 17 00:00:00 2001 From: siilieva Date: Thu, 20 Nov 2025 17:05:52 +0100 Subject: [PATCH 59/99] Fix US MeV to qdc calibaration constants This is based on better understanding of the initial calibration of H6 testbeam data from 2021. See related issue #296 --- geometry/sndLHC_TI18geom_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry/sndLHC_TI18geom_config.py b/geometry/sndLHC_TI18geom_config.py index 48db4b0e68..1870adc651 100644 --- a/geometry/sndLHC_TI18geom_config.py +++ b/geometry/sndLHC_TI18geom_config.py @@ -320,8 +320,8 @@ c.MuFilter.DsAttenuationLength = 350 * u.cm # values between 300 cm and 400cm observed for H6 testbeam c.MuFilter.DsTAttenuationLength = 700 * u.cm # top readout with mirror on bottom c.MuFilter.VandUpAttenuationLength = 999 * u.cm # no significante attenuation observed for H6 testbeam - c.MuFilter.VandUpSiPMcalibrationL = 25.*1000. # 1.65 MeV = 41 qcd - c.MuFilter.VandUpSiPMcalibrationS = 25.*1000. + c.MuFilter.VandUpSiPMcalibrationL = 50.*1000. # 1.65 MeV = 41 qcd over 6 Large SiPMs(one side) + c.MuFilter.VandUpSiPMcalibrationS = 0. # no MIP signal for small SiPMs, delayed and compromised response in general c.MuFilter.DsSiPMcalibration = 25.*1000. c.MuFilter.timeResol = 150.*u.picosecond c.MuFilter.VandUpPropSpeed = 12.5*u.cm/u.nanosecond From b035b9a2f955942ebe065009afc668adb0813a8b Mon Sep 17 00:00:00 2001 From: siilieva Date: Thu, 20 Nov 2025 17:25:48 +0100 Subject: [PATCH 60/99] MuFilterHit:Make selection of channels with positive signal explicit Selection of positive qdc was hidden and not optional in numerous class member functions related to timing. Even if this criterion is very reasonable, I think it is better to make it visible and configurable. The default is to use only channels having positive signal. --- shipLHC/MuFilterHit.cxx | 22 +++++++++++----------- shipLHC/MuFilterHit.h | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/shipLHC/MuFilterHit.cxx b/shipLHC/MuFilterHit.cxx index 4ad2159802..03ac956e48 100644 --- a/shipLHC/MuFilterHit.cxx +++ b/shipLHC/MuFilterHit.cxx @@ -169,13 +169,13 @@ std::map MuFilterHit::GetAllSignals(Bool_t mask,Bool_t positive) } // ----- Public method Get List of time measurements ------------------------------------------- -std::map MuFilterHit::GetAllTimes(Bool_t mask) +std::map MuFilterHit::GetAllTimes(Bool_t mask,Bool_t positive) { std::map allTimes; for (unsigned int s=0; s 0){ + if (signals[channel]> 0 || !positive){ if (!fMasked[channel] || !mask){ allTimes[channel] = times[channel]; } @@ -186,7 +186,7 @@ std::map MuFilterHit::GetAllTimes(Bool_t mask) } // ----- Public method Get time difference mean Left - mean Right ----------------- -Float_t MuFilterHit::GetDeltaT(Bool_t mask) +Float_t MuFilterHit::GetDeltaT(Bool_t mask,Bool_t positive) // based on mean TDC measured on Left and Right { Float_t mean[] = {0,0}; @@ -195,7 +195,7 @@ Float_t MuFilterHit::GetDeltaT(Bool_t mask) for (unsigned int s=0; s 0){ + if (signals[channel]> 0 || !positive){ if (!fMasked[channel] || !mask){ mean[s] += times[channel]; count[s] += 1; @@ -208,7 +208,7 @@ Float_t MuFilterHit::GetDeltaT(Bool_t mask) } return dT; } -Float_t MuFilterHit::GetFastDeltaT(Bool_t mask) +Float_t MuFilterHit::GetFastDeltaT(Bool_t mask,Bool_t positive) // based on fastest (earliest) TDC measured on Left and Right { Float_t first[] = {1E20,1E20}; @@ -216,7 +216,7 @@ Float_t MuFilterHit::GetFastDeltaT(Bool_t mask) for (unsigned int s=0; s 0){ + if (signals[channel]> 0 || !positive){ if (!fMasked[channel] || !mask){ if (times[channel] 0){ + if (signals[channel]> 0 || !positive){ if (!fMasked[channel] || !mask){ mean[s] += times[channel]; count[s] += 1; @@ -263,12 +263,12 @@ Float_t MuFilterHit::GetImpactT(Bool_t mask) } // ----- Public method Get position of impact point along the bar ----------------- -Float_t MuFilterHit::GetImpactXpos(Bool_t mask, Bool_t isMC) +Float_t MuFilterHit::GetImpactXpos(Bool_t mask,Bool_t positive,Bool_t isMC) { if ( nSides!=2 ){ return -999.; } - Float_t dT = GetDeltaT(mask); + Float_t dT = GetDeltaT(mask,positive); if (dT==-999.){ return -999.; } @@ -307,7 +307,7 @@ std::map MuFilterHit::SumOfSignals(Bool_t mask) for (unsigned int s=0; s 0){ + if (signals[channel]> 0){ // makes sense to sum up positive signals only if (!fMasked[channel] || !mask){ if (s==0 and !isShort(j)){theSumL+= signals[channel];} if (s==0 and isShort(j)){theSumLS+= signals[channel];} diff --git a/shipLHC/MuFilterHit.h b/shipLHC/MuFilterHit.h index dc97edb1e9..3812ffc39a 100644 --- a/shipLHC/MuFilterHit.h +++ b/shipLHC/MuFilterHit.h @@ -31,11 +31,11 @@ class MuFilterHit : public SndlhcHit Float_t SumOfSignals(char* opt,Bool_t mask=kTRUE); std::map SumOfSignals(Bool_t mask=kTRUE); std::map GetAllSignals(Bool_t mask=kTRUE,Bool_t positive=kTRUE); - std::map GetAllTimes(Bool_t mask=kTRUE); - Float_t GetDeltaT(Bool_t mask=kTRUE); - Float_t GetFastDeltaT(Bool_t mask=kTRUE); - Float_t GetImpactT(Bool_t mask=kTRUE); - Float_t GetImpactXpos(Bool_t mask=kTRUE, Bool_t isMC=kFALSE); + std::map GetAllTimes(Bool_t mask=kTRUE,Bool_t positive=kTRUE); + Float_t GetDeltaT(Bool_t mask=kTRUE,Bool_t positive=kTRUE); + Float_t GetFastDeltaT(Bool_t mask=kTRUE,Bool_t positive=kTRUE); + Float_t GetImpactT(Bool_t mask=kTRUE,Bool_t positive=kTRUE); + Float_t GetImpactXpos(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t isMC=kFALSE); bool isValid() const {return flag;} bool isMasked(Int_t i) const {return fMasked[i];} void SetMasked(Int_t i) {fMasked[i]=kTRUE;} From a2931b99b5714d9af821d5c3de72de11def046bd Mon Sep 17 00:00:00 2001 From: siilieva Date: Thu, 20 Nov 2025 17:33:46 +0100 Subject: [PATCH 61/99] MuFilterHit: Add option to skip small SiPMs in numerous functions Skipping small SiPMs is the default behaviour since these channels have delayed and polluted response, see #291 and #296. Beware that we have two words for small SiPMs: small and short. They both refer to and small SiPM both refer the SiPM of 3x3mm2 size. The original commit from 2021 adding isShort() method sets the tone to use 'short'. In subsequent commits, notes, papers, and in verbal communication however, we often use 'small', so we stick to 'small'. --- shipLHC/MuFilterHit.cxx | 39 +++++++++++++++++++++++++-------------- shipLHC/MuFilterHit.h | 16 ++++++++-------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/shipLHC/MuFilterHit.cxx b/shipLHC/MuFilterHit.cxx index 03ac956e48..478ea2ada3 100644 --- a/shipLHC/MuFilterHit.cxx +++ b/shipLHC/MuFilterHit.cxx @@ -105,7 +105,7 @@ MuFilterHit::MuFilterHit(Int_t detID, std::vector V) // In the SndlhcHit class the 'signals' array starts from 0. for (unsigned int j=0; jGaus(earliestToAL, timeResol); }else{ signals[j] = signalLeft/float(nSiPMs) * siPMcalibration; // most simplest model, divide signal individually. @@ -126,11 +126,12 @@ MuFilterHit::~MuFilterHit() { } // ------------------------------------------------------------------------- // ----- Public method GetEnergy ------------------------------------------- -Float_t MuFilterHit::GetEnergy() +Float_t MuFilterHit::GetEnergy(Bool_t use_small_sipms) { // to be calculated from digis and calibration constants, missing! Float_t E = 0; for (unsigned int j=0; j1){ E+=signals[j+nSiPMs];} } @@ -151,7 +152,7 @@ bool MuFilterHit::isShort(Int_t i){ } // ----- Public method Get List of signals ------------------------------------------- -std::map MuFilterHit::GetAllSignals(Bool_t mask,Bool_t positive) +std::map MuFilterHit::GetAllSignals(Bool_t mask,Bool_t positive,Bool_t use_small_sipms) { std::map allSignals; for (unsigned int s=0; s MuFilterHit::GetAllSignals(Bool_t mask,Bool_t positive) if (signals[channel]<-900){continue;} if (signals[channel]> 0 || !positive){ if (!fMasked[channel] || !mask){ + if (!isShort(channel) || use_small_sipms){ allSignals[channel] = signals[channel]; } + } } } } @@ -169,7 +172,7 @@ std::map MuFilterHit::GetAllSignals(Bool_t mask,Bool_t positive) } // ----- Public method Get List of time measurements ------------------------------------------- -std::map MuFilterHit::GetAllTimes(Bool_t mask,Bool_t positive) +std::map MuFilterHit::GetAllTimes(Bool_t mask,Bool_t positive,Bool_t use_small_sipms) { std::map allTimes; for (unsigned int s=0; s MuFilterHit::GetAllTimes(Bool_t mask,Bool_t positive) unsigned int channel = j+s*nSiPMs; if (signals[channel]> 0 || !positive){ if (!fMasked[channel] || !mask){ + if (!isShort(channel) || use_small_sipms){ allTimes[channel] = times[channel]; } + } } } } @@ -186,7 +191,7 @@ std::map MuFilterHit::GetAllTimes(Bool_t mask,Bool_t positive) } // ----- Public method Get time difference mean Left - mean Right ----------------- -Float_t MuFilterHit::GetDeltaT(Bool_t mask,Bool_t positive) +Float_t MuFilterHit::GetDeltaT(Bool_t mask,Bool_t positive,Bool_t use_small_sipms) // based on mean TDC measured on Left and Right { Float_t mean[] = {0,0}; @@ -197,9 +202,11 @@ Float_t MuFilterHit::GetDeltaT(Bool_t mask,Bool_t positive) unsigned int channel = j+s*nSiPMs; if (signals[channel]> 0 || !positive){ if (!fMasked[channel] || !mask){ + if (!isShort(channel) || use_small_sipms){ mean[s] += times[channel]; count[s] += 1; } + } } } } @@ -208,7 +215,7 @@ Float_t MuFilterHit::GetDeltaT(Bool_t mask,Bool_t positive) } return dT; } -Float_t MuFilterHit::GetFastDeltaT(Bool_t mask,Bool_t positive) +Float_t MuFilterHit::GetFastDeltaT(Bool_t mask,Bool_t positive,Bool_t use_small_sipms) // based on fastest (earliest) TDC measured on Left and Right { Float_t first[] = {1E20,1E20}; @@ -218,8 +225,10 @@ Float_t MuFilterHit::GetFastDeltaT(Bool_t mask,Bool_t positive) unsigned int channel = j+s*nSiPMs; if (signals[channel]> 0 || !positive){ if (!fMasked[channel] || !mask){ - if (times[channel] 0 || !positive){ if (!fMasked[channel] || !mask){ - mean[s] += times[channel]; - count[s] += 1; - } + if (!isShort(channel) || use_small_sipms){ + mean[s] += times[channel]; + count[s] += 1; + } + } } } } @@ -263,12 +274,12 @@ Float_t MuFilterHit::GetImpactT(Bool_t mask,Bool_t positive) } // ----- Public method Get position of impact point along the bar ----------------- -Float_t MuFilterHit::GetImpactXpos(Bool_t mask,Bool_t positive,Bool_t isMC) +Float_t MuFilterHit::GetImpactXpos(Bool_t mask,Bool_t positive,Bool_t use_small_sipms,Bool_t isMC) { if ( nSides!=2 ){ return -999.; } - Float_t dT = GetDeltaT(mask,positive); + Float_t dT = GetDeltaT(mask,positive,use_small_sipms); if (dT==-999.){ return -999.; } diff --git a/shipLHC/MuFilterHit.h b/shipLHC/MuFilterHit.h index 3812ffc39a..e885f75d23 100644 --- a/shipLHC/MuFilterHit.h +++ b/shipLHC/MuFilterHit.h @@ -27,22 +27,22 @@ class MuFilterHit : public SndlhcHit /** Output to screen **/ void Print() const; - Float_t GetEnergy(); + Float_t GetEnergy(Bool_t use_small_sipms=kFALSE); Float_t SumOfSignals(char* opt,Bool_t mask=kTRUE); std::map SumOfSignals(Bool_t mask=kTRUE); - std::map GetAllSignals(Bool_t mask=kTRUE,Bool_t positive=kTRUE); - std::map GetAllTimes(Bool_t mask=kTRUE,Bool_t positive=kTRUE); - Float_t GetDeltaT(Bool_t mask=kTRUE,Bool_t positive=kTRUE); - Float_t GetFastDeltaT(Bool_t mask=kTRUE,Bool_t positive=kTRUE); - Float_t GetImpactT(Bool_t mask=kTRUE,Bool_t positive=kTRUE); - Float_t GetImpactXpos(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t isMC=kFALSE); + std::map GetAllSignals(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t use_small_sipms=kFALSE); + std::map GetAllTimes(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t use_small_sipms=kFALSE); + Float_t GetDeltaT(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t use_small_sipms=kFALSE); + Float_t GetFastDeltaT(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t use_small_sipms=kFALSE); + Float_t GetImpactT(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t use_small_sipms=kFALSE); + Float_t GetImpactXpos(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t use_small_sipms=kFALSE,Bool_t isMC=kFALSE); bool isValid() const {return flag;} bool isMasked(Int_t i) const {return fMasked[i];} void SetMasked(Int_t i) {fMasked[i]=kTRUE;} int GetSystem(){return floor(fDetectorID/10000);} int GetPlane(){return int(fDetectorID/1000)%10;} bool isVertical(); - bool isShort(Int_t); + bool isShort(Int_t);// short==small sipm private: Float_t flag; ///< flag From 7273f81e817a97cc5dda45fea50c84283adf2031 Mon Sep 17 00:00:00 2001 From: siilieva Date: Thu, 20 Nov 2025 17:34:31 +0100 Subject: [PATCH 62/99] MuFilterHit: remove code that is never used --- shipLHC/MuFilterHit.cxx | 1 - shipLHC/MuFilterHit.h | 1 - 2 files changed, 2 deletions(-) diff --git a/shipLHC/MuFilterHit.cxx b/shipLHC/MuFilterHit.cxx index 478ea2ada3..1ee928fc57 100644 --- a/shipLHC/MuFilterHit.cxx +++ b/shipLHC/MuFilterHit.cxx @@ -9,7 +9,6 @@ #include #include -Double_t speedOfLight = TMath::C() *100./1000000000.0 ; // from m/sec to cm/ns // ----- Default constructor ------------------------------------------- MuFilterHit::MuFilterHit() : SndlhcHit() diff --git a/shipLHC/MuFilterHit.h b/shipLHC/MuFilterHit.h index e885f75d23..4c5e1e0363 100644 --- a/shipLHC/MuFilterHit.h +++ b/shipLHC/MuFilterHit.h @@ -28,7 +28,6 @@ class MuFilterHit : public SndlhcHit /** Output to screen **/ void Print() const; Float_t GetEnergy(Bool_t use_small_sipms=kFALSE); - Float_t SumOfSignals(char* opt,Bool_t mask=kTRUE); std::map SumOfSignals(Bool_t mask=kTRUE); std::map GetAllSignals(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t use_small_sipms=kFALSE); std::map GetAllTimes(Bool_t mask=kTRUE,Bool_t positive=kTRUE,Bool_t use_small_sipms=kFALSE); From ecbf1d3b0022bb2f2652a87d757cabc57a643b51 Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 25 Nov 2025 14:28:03 +0100 Subject: [PATCH 63/99] USPlane:add use_small_sipms flag manage usage of small SiPMs --- analysis/tools/sndPlaneTools.cxx | 4 ++-- analysis/tools/sndPlaneTools.h | 2 +- analysis/tools/sndUSPlane.cxx | 35 +++++++++++++++++++++----------- analysis/tools/sndUSPlane.h | 2 +- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/analysis/tools/sndPlaneTools.cxx b/analysis/tools/sndPlaneTools.cxx index 587418beb0..e3a68eb8db 100644 --- a/analysis/tools/sndPlaneTools.cxx +++ b/analysis/tools/sndPlaneTools.cxx @@ -36,7 +36,7 @@ std::vector snd::analysis_tools::FillScifi(cons } -std::vector snd::analysis_tools::FillUS(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool isMC) +std::vector snd::analysis_tools::FillUS(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool isMC, bool use_small_sipms) { std::vector us_planes; @@ -55,7 +55,7 @@ std::vector snd::analysis_tools::FillUS(const snd: else throw std::runtime_error{"Invalid US plane"}; } for (int st{0}; st < n_station; ++st) { - us_planes.emplace_back(snd::analysis_tools::USPlane(plane_hits[st], configuration, mufilter_geometry, st+1, isMC)); + us_planes.emplace_back(snd::analysis_tools::USPlane(plane_hits[st], configuration, mufilter_geometry, st+1, isMC, use_small_sipms)); } return us_planes; } diff --git a/analysis/tools/sndPlaneTools.h b/analysis/tools/sndPlaneTools.h index 9c93ba674a..1366ab1d53 100644 --- a/analysis/tools/sndPlaneTools.h +++ b/analysis/tools/sndPlaneTools.h @@ -14,7 +14,7 @@ namespace snd { namespace analysis_tools { // Produce scifi and us planes from data std::vector FillScifi(const Configuration &configuration, TClonesArray *sf_hits, Scifi *scifi_geometry); - std::vector FillUS(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool isMC=false); + std::vector FillUS(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool isMC=false, bool use_small_sipms=false); } } diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index 5ebb677676..f8d5dd311f 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -10,7 +10,7 @@ #include "MuFilterHit.h" #include "ShipUnit.h" -snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool isMC) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) +snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool isMC, bool use_small_sipms_sipms) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) { for ( auto mu_hit : snd_hits) { @@ -23,19 +23,30 @@ snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const USHit hit; hit.bar = static_cast(detectorID % 1000); hit.channel_index = 16 * hit.bar + i; - hit.timestamp = mu_hit->GetTime(i); - hit.qdc = mu_hit->GetSignal(i); hit.is_large = !mu_hit->isShort(i); hit.is_right = i > 7 ? true : false; - - // use the left and right measurements to calculate the x coordinate along the bar - float timeConversion = 1.; - if (!isMC) { - timeConversion = ShipUnit::snd_TDC2ns; - } - hit.x = A.X() - 0.5*(mu_hit->GetDeltaT()*timeConversion*configuration_.us_signal_speed+configuration_.us_bar_length); - hit.y = A.Y(); - hit.z = A.Z(); + + if (!hit.is_large && !use_small_sipms_sipms) + { + hit.timestamp = std::nan(""); + hit.qdc = std::nan(""); + hit.x = std::nan(""); + hit.y = std::nan(""); + hit.z = std::nan(""); + } + else + { + hit.timestamp = mu_hit->GetTime(i); + hit.qdc = mu_hit->GetSignal(i); + // use the left and right measurements to calculate the x coordinate along the bar + float timeConversion = 1.; + if (!isMC) { + timeConversion = ShipUnit::snd_TDC2ns; + } + hit.x = A.X() - 0.5*(mu_hit->GetDeltaT()*timeConversion*configuration_.us_signal_speed+configuration_.us_bar_length); + hit.y = A.Y(); + hit.z = A.Z(); + } hits_.push_back(hit); } } diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h index 03b192414d..edb00b885d 100644 --- a/analysis/tools/sndUSPlane.h +++ b/analysis/tools/sndUSPlane.h @@ -45,7 +45,7 @@ namespace snd { bool is_right; }; - USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool isMC=false); + USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool isMC=false, bool use_small_sipms=false); const sl_pair GetNHits() const; const int GetStation() const { return station_; }; From 6e1e77421d22ba20f4546629cd7c4aacb0eabc09 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 5 Dec 2025 10:41:57 +0100 Subject: [PATCH 64/99] run_digi: remove hardcoded US MeV->qdc const This is now in the geofile since 2022 and is obsolete in the run_digiSND. Mind the name VandUpSiPMcalibration in the geofile is VandUpSiPMcalibrationL --- shipLHC/run_digiSND.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/shipLHC/run_digiSND.py b/shipLHC/run_digiSND.py index 4dd1432be3..128e63e1f7 100644 --- a/shipLHC/run_digiSND.py +++ b/shipLHC/run_digiSND.py @@ -75,8 +75,6 @@ def mem_monitor(): mufiDet.SetConfPar("MuFilter/DsTAttenuationLength",700 * u.cm) # top readout with mirror on bottom mufiDet.SetConfPar("MuFilter/VandUpAttenuationLength",999 * u.cm) # no significante attenuation observed for H6 testbeam mufiDet.SetConfPar("MuFilter/DsSiPMcalibrationS",25.*1000.) # in MC: 1.65 keV are about 41.2 qdc -mufiDet.SetConfPar("MuFilter/VandUpSiPMcalibration",25.*1000.); -mufiDet.SetConfPar("MuFilter/VandUpSiPMcalibrationS",25.*1000.); mufiDet.SetConfPar("MuFilter/VandUpPropSpeed",12.5*u.cm/u.nanosecond); mufiDet.SetConfPar("MuFilter/DsPropSpeed",14.3*u.cm/u.nanosecond); scifiDet.SetConfPar("Scifi/nphe_min",options.ts) # threshold From 6da5cba76443de230c19c98e26f133b05aea00e7 Mon Sep 17 00:00:00 2001 From: siilieva Date: Mon, 8 Dec 2025 15:05:07 +0100 Subject: [PATCH 65/99] MuFilterHit: Only US has short SiPMs, fix isShort() method This was fixed on the digitization side in commit 0570397. --- shipLHC/MuFilterHit.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shipLHC/MuFilterHit.cxx b/shipLHC/MuFilterHit.cxx index 1ee928fc57..58c35ec6b6 100644 --- a/shipLHC/MuFilterHit.cxx +++ b/shipLHC/MuFilterHit.cxx @@ -146,7 +146,8 @@ bool MuFilterHit::isVertical(){ } bool MuFilterHit::isShort(Int_t i){ - if (i%8==2 || i%8==5) {return kTRUE;} + // only US has short SiPMs + if (GetSystem()==2 && (i%8==2 || i%8==5)) {return kTRUE;} else{return kFALSE;} } From f4a90ac7e84f36fbe9d5457005727a37118e7b3a Mon Sep 17 00:00:00 2001 From: fmei Date: Tue, 16 Dec 2025 13:56:09 +0100 Subject: [PATCH 66/99] update data and geo csv for 2025 --- analysis/tools/data_paths.csv | 10 +++++++++- analysis/tools/geo_paths.csv | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/analysis/tools/data_paths.csv b/analysis/tools/data_paths.csv index f91e4da5f4..b68c64c326 100644 --- a/analysis/tools/data_paths.csv +++ b/analysis/tools/data_paths.csv @@ -1,5 +1,5 @@ min_run_number,max_run_number,path -0,5421,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2022/ +4361,5421,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2022/ 5422,7356,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2023/ 7649,8317,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2024/run_241/ 8318,8580,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2024/run_242/ @@ -13,5 +13,13 @@ min_run_number,max_run_number,path 9692,9880,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2024/run_2410/ 9881,10011,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2024/run_2411/ 10012,10422,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2024/run_2412/ +10919,11156,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2025/run_251/ +11158,11575,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2025/run_252/ +11576,11675,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2025/run_253/ +11676,11794,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2025/run_254/ +11795,12019,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2025/run_255/ +12021,12121,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2025/run_256/ +12123,12411,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2025/run_257/ +12412,12792,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2025/run_258/ 100238,100679,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/commissioning/testbeam_June2023_H8/ 100841,100985,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/commissioning/testbeam_24/ \ No newline at end of file diff --git a/analysis/tools/geo_paths.csv b/analysis/tools/geo_paths.csv index 0682dc9daa..4588ed00b0 100644 --- a/analysis/tools/geo_paths.csv +++ b/analysis/tools/geo_paths.csv @@ -1,7 +1,8 @@ min_run_number,max_run_number,path -0,5422,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2022/geofile_sndlhc_TI18_V4_2022.root +4361,5422,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2022/geofile_sndlhc_TI18_V4_2022.root 5482,7356,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2023/geofile_sndlhc_TI18_V3_2023.root 7357,10422,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2024/geofile_sndlhc_TI18_V12_2024.root +10919,12792,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/physics/2025/geofile_sndlhc_TI18_V8_2025.root 100238,100679,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/commissioning/testbeam_June2023_H8/geofile_sndlhc_H8_2023_3walls.root 100841,100953,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/commissioning/testbeam_24/geofile_sndlhc_H4_2024_W_2walls.root 100954,100985,root://eospublic.cern.ch//eos/experiment/sndlhc/convertedData/commissioning/testbeam_24/geofile_sndlhc_H4_2024_Fe_1wall.root From a8c8535cafc4ba393321fe6cae87523f86bf1945 Mon Sep 17 00:00:00 2001 From: fmei Date: Mon, 15 Dec 2025 18:07:21 +0100 Subject: [PATCH 67/99] add is_mc option to sndConfiguration --- analysis/tools/sndConfiguration.cxx | 20 ++++++++++++-------- analysis/tools/sndConfiguration.h | 4 +++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/analysis/tools/sndConfiguration.cxx b/analysis/tools/sndConfiguration.cxx index b874c1fa47..9f4a3badf1 100644 --- a/analysis/tools/sndConfiguration.cxx +++ b/analysis/tools/sndConfiguration.cxx @@ -7,7 +7,7 @@ #include "Scifi.h" #include "MuFilter.h" -snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter *muon_filter_geometry) +snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter *muon_filter_geometry, bool is_MC) : is_mc(is_MC) { // Parameters from geometry scifi_n_stations = scifi_geometry->GetConfParI("Scifi/nscifi"); @@ -159,20 +159,24 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter snd::Configuration::Option snd::Configuration::GetOption(int run_number) { - if (run_number >= 100840) { + if (run_number >= 100841 && run_number <= 100985) { std::cout << "Choosing option >>>>>>>>>>\t test_beam_2024 \t<<<<<<<<<<" <= 100000) { + else if (run_number >= 100238 && run_number <= 100679) { std::cout << "Choosing option >>>>>>>>>>\t test_beam_2023 \t<<<<<<<<<<" <= 7357 && run_number <= 12792) { + std::cout << "Choosing option >>>>>>>>>>\t ti18_2024_2025 \t<<<<<<<<<<" <= 4361 && run_number <= 7356){ std::cout << "Choosing option >>>>>>>>>>\t ti18_2022_2023 \t<<<<<<<<<<" <>>>>>>>>>\t ti18_2024_2025 \t<<<<<<<<<<" < Date: Mon, 15 Dec 2025 18:43:28 +0100 Subject: [PATCH 68/99] use is_mc directly from configuration --- analysis/tools/sndPlaneTools.cxx | 4 ++-- analysis/tools/sndPlaneTools.h | 2 +- analysis/tools/sndScifiPlane.cxx | 7 ++++++- analysis/tools/sndUSPlane.cxx | 9 +++------ analysis/tools/sndUSPlane.h | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/analysis/tools/sndPlaneTools.cxx b/analysis/tools/sndPlaneTools.cxx index e3a68eb8db..6bd3f118c1 100644 --- a/analysis/tools/sndPlaneTools.cxx +++ b/analysis/tools/sndPlaneTools.cxx @@ -36,7 +36,7 @@ std::vector snd::analysis_tools::FillScifi(cons } -std::vector snd::analysis_tools::FillUS(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool isMC, bool use_small_sipms) +std::vector snd::analysis_tools::FillUS(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool use_small_sipms) { std::vector us_planes; @@ -55,7 +55,7 @@ std::vector snd::analysis_tools::FillUS(const snd: else throw std::runtime_error{"Invalid US plane"}; } for (int st{0}; st < n_station; ++st) { - us_planes.emplace_back(snd::analysis_tools::USPlane(plane_hits[st], configuration, mufilter_geometry, st+1, isMC, use_small_sipms)); + us_planes.emplace_back(snd::analysis_tools::USPlane(plane_hits[st], configuration, mufilter_geometry, st+1, use_small_sipms)); } return us_planes; } diff --git a/analysis/tools/sndPlaneTools.h b/analysis/tools/sndPlaneTools.h index 1366ab1d53..f442a79503 100644 --- a/analysis/tools/sndPlaneTools.h +++ b/analysis/tools/sndPlaneTools.h @@ -14,7 +14,7 @@ namespace snd { namespace analysis_tools { // Produce scifi and us planes from data std::vector FillScifi(const Configuration &configuration, TClonesArray *sf_hits, Scifi *scifi_geometry); - std::vector FillUS(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool isMC=false, bool use_small_sipms=false); + std::vector FillUS(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool use_small_sipms=false); } } diff --git a/analysis/tools/sndScifiPlane.cxx b/analysis/tools/sndScifiPlane.cxx index 7bf9452c9a..f0569b57a7 100644 --- a/analysis/tools/sndScifiPlane.cxx +++ b/analysis/tools/sndScifiPlane.cxx @@ -20,7 +20,12 @@ snd::analysis_tools::ScifiPlane::ScifiPlane(std::vector snd_hits, ScifiHit hit; hit.channel_index = 512 * snd_hit->GetMat() + 128 * snd_hit->GetSiPM() + snd_hit->GetSiPMChan(); int detectorID = snd_hit->GetDetectorID(); - hit.timestamp = (scifi_geometry->GetCorrectedTime(detectorID, snd_hit->GetTime(0)*ShipUnit::snd_TDC2ns, 0) / ShipUnit::snd_TDC2ns); // timestamp is in clock cycles + if (configuration_.is_mc) { + hit.timestamp = snd_hit->GetTime(0) / ShipUnit::snd_TDC2ns; // timestamp is in clock cycles + } + else { + hit.timestamp = (scifi_geometry->GetCorrectedTime(detectorID, snd_hit->GetTime(0)*ShipUnit::snd_TDC2ns, 0) / ShipUnit::snd_TDC2ns); // timestamp is in clock cycles + } hit.qdc = snd_hit->GetSignal(0); hit.is_x = snd_hit->isVertical(); diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index f8d5dd311f..8b484e7b76 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -10,7 +10,7 @@ #include "MuFilterHit.h" #include "ShipUnit.h" -snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool isMC, bool use_small_sipms_sipms) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) +snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool use_small_sipms_sipms) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) { for ( auto mu_hit : snd_hits) { @@ -36,13 +36,10 @@ snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const } else { - hit.timestamp = mu_hit->GetTime(i); + hit.timestamp = configuration_.is_mc ? mu_hit->GetTime(i) / ShipUnit::snd_TDC2ns : mu_hit->GetTime(i); hit.qdc = mu_hit->GetSignal(i); // use the left and right measurements to calculate the x coordinate along the bar - float timeConversion = 1.; - if (!isMC) { - timeConversion = ShipUnit::snd_TDC2ns; - } + float timeConversion = configuration_.is_mc ? 1. : ShipUnit::snd_TDC2ns; hit.x = A.X() - 0.5*(mu_hit->GetDeltaT()*timeConversion*configuration_.us_signal_speed+configuration_.us_bar_length); hit.y = A.Y(); hit.z = A.Z(); diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h index edb00b885d..22069c5a5c 100644 --- a/analysis/tools/sndUSPlane.h +++ b/analysis/tools/sndUSPlane.h @@ -37,7 +37,7 @@ namespace snd { double qdc; double timestamp; - double x; // position of right or left side of bar + double x; double y; double z; @@ -45,7 +45,7 @@ namespace snd { bool is_right; }; - USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool isMC=false, bool use_small_sipms=false); + USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool use_small_sipms=false); const sl_pair GetNHits() const; const int GetStation() const { return station_; }; From 663d48c86d26a1b102f4da2c8b76ad4a4227778e Mon Sep 17 00:00:00 2001 From: fmei Date: Mon, 15 Dec 2025 18:47:03 +0100 Subject: [PATCH 69/99] remove unnecessary ; --- analysis/tools/sndScifiPlane.h | 6 +++--- analysis/tools/sndUSPlane.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/analysis/tools/sndScifiPlane.h b/analysis/tools/sndScifiPlane.h index 5f90a9eecf..9994f1f167 100644 --- a/analysis/tools/sndScifiPlane.h +++ b/analysis/tools/sndScifiPlane.h @@ -34,9 +34,9 @@ namespace snd { ScifiPlane(std::vector snd_hits, const Configuration &configuration, Scifi *scifi_geometry, int station); - const int GetStation() const { return station_; }; - const std::vector GetHits() const { return hits_; }; - const ROOT::Math::XYZPoint GetCentroid() const { return centroid_; }; + const int GetStation() const { return station_; } + const std::vector GetHits() const { return hits_; } + const ROOT::Math::XYZPoint GetCentroid() const { return centroid_; } const ROOT::Math::XYZPoint GetCentroidError() const { return centroid_error_; } const xy_pair GetTotQdc(bool only_positive = false) const; const xy_pair GetTotEnergy(bool only_positive = false) const; diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h index 22069c5a5c..df026e2d3d 100644 --- a/analysis/tools/sndUSPlane.h +++ b/analysis/tools/sndUSPlane.h @@ -48,7 +48,7 @@ namespace snd { USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool use_small_sipms=false); const sl_pair GetNHits() const; - const int GetStation() const { return station_; }; + const int GetStation() const { return station_; } const sl_pair GetTotQdc() const; const sl_pair GetTotEnergy() const; @@ -56,11 +56,11 @@ namespace snd { const rl_pair GetBarQdc(int bar_to_compute) const; const sl_pair GetBarNHits(int bar_to_compute) const; const std::vector GetHits() const { return hits_; }; - double HasShower() const { return GetNHits().large >= configuration_.us_min_n_hits_for_centroid; }; + double HasShower() const { return GetNHits().large >= configuration_.us_min_n_hits_for_centroid; } // The centroid is the qdc-weighted mean of hit positions, considering only hits with positive qdc void FindCentroid(); - ROOT::Math::XYZPoint GetCentroid() const { return centroid_; }; - ROOT::Math::XYZPoint GetCentroidError() const { return centroid_error_; }; + ROOT::Math::XYZPoint GetCentroid() const { return centroid_; } + ROOT::Math::XYZPoint GetCentroidError() const { return centroid_error_; } const int GetNHitBars() const; From 0d5660e950a4d4a37c2a3109f9d032e5e26815e1 Mon Sep 17 00:00:00 2001 From: cvilelahep Date: Fri, 9 Jan 2026 13:38:24 +0100 Subject: [PATCH 70/99] Updating large SiPM callibration constant name in MuFilterHit class The variable was being set manually in `run_digiSND.py` until 6e1e774 which removed the hard-coded value as it was already set in the geometry file. However, as stated in the commit message, the name of the variable that is set in the geometry file is different from variable name that was hard-coded. This commit updates the variable name in the MuFilterHit class so that the calibration constant is correctly read from the geometry file. As far as I can tell, this is the only place in `sndsw` where this variable is used. --- shipLHC/MuFilterHit.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shipLHC/MuFilterHit.cxx b/shipLHC/MuFilterHit.cxx index 58c35ec6b6..42d08bf46f 100644 --- a/shipLHC/MuFilterHit.cxx +++ b/shipLHC/MuFilterHit.cxx @@ -63,7 +63,7 @@ MuFilterHit::MuFilterHit(Int_t detID, std::vector V) attLength = 2*MuFilterDet->GetConfParF("MuFilter/VandUpAttenuationLength"); } else {attLength = MuFilterDet->GetConfParF("MuFilter/VandUpAttenuationLength");} - siPMcalibration = MuFilterDet->GetConfParF("MuFilter/VandUpSiPMcalibration"); + siPMcalibration = MuFilterDet->GetConfParF("MuFilter/VandUpSiPMcalibrationL"); siPMcalibrationS = MuFilterDet->GetConfParF("MuFilter/VandUpSiPMcalibrationS"); propspeed = MuFilterDet->GetConfParF("MuFilter/VandUpPropSpeed"); } From 5c1628eadda4cbe57c368545d9ea45d6b427ecb7 Mon Sep 17 00:00:00 2001 From: fmei Date: Thu, 15 Jan 2026 12:04:47 +0100 Subject: [PATCH 71/99] not used --- analysis/tools/sndShowerTools.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/analysis/tools/sndShowerTools.cxx b/analysis/tools/sndShowerTools.cxx index 97d9789075..9b064b76f1 100644 --- a/analysis/tools/sndShowerTools.cxx +++ b/analysis/tools/sndShowerTools.cxx @@ -244,7 +244,6 @@ std::pair snd::analysis_tools::GetSciFiSpatialAnisotropy(const s double lambda1, lambda2; std::tie(lambda1, lambda2) = PCA(x, zx); double anisotropy_xz = (lambda1 > 0) ? 1.0 - lambda2 / lambda1 : 0.0; - auto pca_result_yz = PCA(y, zy); std::tie(lambda1, lambda2) = PCA(y, zy); double anisotropy_yz = (lambda1 > 0) ? 1.0 - lambda2 / lambda1 : 0.0; return {anisotropy_xz, anisotropy_yz}; From 49ab15d223a0d965ea227f8a149d3e4393b0396d Mon Sep 17 00:00:00 2001 From: fmei Date: Thu, 15 Jan 2026 12:41:10 +0100 Subject: [PATCH 72/99] fix warnigns --- analysis/tools/sndSciFiTools.cxx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/analysis/tools/sndSciFiTools.cxx b/analysis/tools/sndSciFiTools.cxx index 3709bcd046..c8ee2a5ec6 100644 --- a/analysis/tools/sndSciFiTools.cxx +++ b/analysis/tools/sndSciFiTools.cxx @@ -24,7 +24,7 @@ void snd::analysis_tools::getSciFiHitsPerStation(const TClonesArray *digiHits, s sndScifiHit *hit; TIter hitIterator(digiHits); - while (hit = dynamic_cast(hitIterator.Next())) { + while ((hit = dynamic_cast(hitIterator.Next()))) { if (hit->isValid()) { int station = hit->GetStation(); if (hit->isVertical()) { @@ -135,9 +135,9 @@ float snd::analysis_tools::peakScifiTiming(const TClonesArray &digiHits, int bin TH1F ScifiTiming("Timing", "Scifi Timing", bins, min_x, max_x); Scifi *ScifiDet = dynamic_cast (gROOT->GetListOfGlobals()->FindObject("Scifi") ); - auto* hit = static_cast(digiHits[0]); - int refStation = hit->GetStation(); - bool refOrientation = hit->isVertical(); + auto* first_hit = static_cast(digiHits[0]); + int refStation = first_hit->GetStation(); + bool refOrientation = first_hit->isVertical(); float hitTime = -1.0; float timeConversion = 1.; if (!isMC) { @@ -319,7 +319,6 @@ snd::analysis_tools::filterScifiHits(const TClonesArray &digiHits, LOG(info) << "\"TI18\" setup will be used by default, please provide \"H8\" for the Testbeam setup."; } - sndScifiHit *hit; TIter hitIterator(&supportArray); if (method == 0) { @@ -338,8 +337,8 @@ snd::analysis_tools::filterScifiHits(const TClonesArray &digiHits, for (auto station : ROOT::MakeSeq(1, ScifiStations + 1)) { for (auto orientation : {false, true}) { - auto supportArray = selectScifiHits(digiHits, station, orientation, selection_parameters, true, isMC); - for (auto *p : *supportArray) { + auto supportArray_p = selectScifiHits(digiHits, station, orientation, selection_parameters, true, isMC); + for (auto *p : *supportArray_p) { auto *hit = dynamic_cast(p); if (hit->isValid()) { new ((*filteredHits)[filteredHitsIndex++]) sndScifiHit(*hit); From 8775ae4e495f3aa1c13e414f9c5339f595c15b22 Mon Sep 17 00:00:00 2001 From: fmei Date: Thu, 15 Jan 2026 13:07:45 +0100 Subject: [PATCH 73/99] fix Wreturn-type --- shipLHC/MuFilter.cxx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/shipLHC/MuFilter.cxx b/shipLHC/MuFilter.cxx index 9ab91b3f98..e8ef291fbb 100644 --- a/shipLHC/MuFilter.cxx +++ b/shipLHC/MuFilter.cxx @@ -714,16 +714,18 @@ void MuFilter::GetPosition(Int_t fDetectorID, TVector3& vLeft, TVector3& vRight) } Int_t MuFilter::GetnSides(Int_t detID){ int subsystem = floor(detID/10000)-1; - if (subsystem==0){ - // vertical Veto 3 has the readout on the top only - if (detID>=12000) return conf_ints["MuFilter/VetonSides"]-1; - else {return conf_ints["MuFilter/VetonSides"];} - } - if (subsystem==1){return conf_ints["MuFilter/UpstreamnSides"];} - if (subsystem==2){ - if (detID%1000>59) return conf_ints["MuFilter/DownstreamnSides"]-1; - else {return conf_ints["MuFilter/DownstreamnSides"];} - } + switch (subsystem) { + case 0: + // vertical Veto 3 has the readout on the top only + return (detID>=12000) ? conf_ints["MuFilter/VetonSides"]-1 : conf_ints["MuFilter/VetonSides"]; + case 1: + return conf_ints["MuFilter/UpstreamnSides"]; + case 2: + return (detID%1000>59) ? conf_ints["MuFilter/DownstreamnSides"]-1 : conf_ints["MuFilter/DownstreamnSides"]; + default: + LOG(FATAL) << "Unknown subsystem"; + return -1; + } } ClassImp(MuFilter) From 61350fd9f1509308262710f605a30ca774d89cdd Mon Sep 17 00:00:00 2001 From: fmei Date: Fri, 16 Jan 2026 12:45:01 +0100 Subject: [PATCH 74/99] fix warnings --- sndFairTasks/ConvRawData.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sndFairTasks/ConvRawData.cxx b/sndFairTasks/ConvRawData.cxx index 900b335d7d..fb62415f6d 100644 --- a/sndFairTasks/ConvRawData.cxx +++ b/sndFairTasks/ConvRawData.cxx @@ -49,8 +49,8 @@ ConvRawData::ConvRawData() : FairTask("ConvRawData") , fEventTree(nullptr) , boards{} - , fEventHeader(nullptr) , fSNDLHCEventHeader(nullptr) + , fEventHeader(nullptr) , fDigiSciFi(nullptr) , fDigiMuFilter(nullptr) {} @@ -146,7 +146,7 @@ InitStatus ConvRawData::Init() LOG (info) << "Time to set the board mapping " << timerBMap.RealTime(); // Get the FairLogger - FairLogger *logger = FairLogger::GetLogger(); + FairLogger::GetLogger(); eventNumber = fnStart; @@ -336,7 +336,7 @@ void ConvRawData::Process0() system = MufiSystem[board_id][tofpet_id]; key = (tofpet_id%2)*1000 + tofpet_channel; tmp = boardMapsMu["MuFilter"][board.first][slots[tofpet_id]]; - if (debug || !tmp.find("not") == string::npos ) + if (debug || !(tmp.find("not") == string::npos)) { LOG (info) << system << " " << key << " " << board.first << " " << tofpet_id << " " << tofpet_id%2 << " " << tofpet_channel; @@ -630,7 +630,7 @@ void ConvRawData::Process1() system = MufiSystem[board_id][tofpet_id]; key = (tofpet_id%2)*1000 + tofpet_channel; tmp = boardMapsMu["MuFilter"][board_name][slots[tofpet_id]]; - if (debug || !tmp.find("not") == string::npos ) + if (debug || !(tmp.find("not") == string::npos)) { LOG (info) << system << " " << key << " " << board_name << " " << tofpet_id << " " << tofpet_id%2 << " " << tofpet_channel; @@ -778,7 +778,7 @@ void ConvRawData::StartTimeofRun(string Path) char *buff = new char[size]; status = file.Read(offset, size, buff, bytesRead); vector vec; - for (int i = 0; i < size; i++){vec.push_back(buff[i]);} + for (size_t i = 0; i < size; i++){vec.push_back(buff[i]);} j = json::parse(vec); status = file.Close(); delete info; @@ -826,7 +826,7 @@ void ConvRawData::DetMapping(string Path) char *buff = new char[size]; status = file.Read(offset, size, buff, bytesRead); vector vec; - for (int i = 0; i < size; i++){vec.push_back(buff[i]);} + for (size_t i = 0; i < size; i++){vec.push_back(buff[i]);} j = json::parse(vec); status = file.Close(); delete info; From ecd58690f81dfde7e9422e32ae0015aa0c5a3c66 Mon Sep 17 00:00:00 2001 From: fmei Date: Fri, 16 Jan 2026 15:48:50 +0100 Subject: [PATCH 75/99] remove unused GetLogger --- sndFairTasks/ConvRawData.cxx | 3 --- 1 file changed, 3 deletions(-) diff --git a/sndFairTasks/ConvRawData.cxx b/sndFairTasks/ConvRawData.cxx index fb62415f6d..32d897f2fd 100644 --- a/sndFairTasks/ConvRawData.cxx +++ b/sndFairTasks/ConvRawData.cxx @@ -145,9 +145,6 @@ InitStatus ConvRawData::Init() timerBMap.Stop(); LOG (info) << "Time to set the board mapping " << timerBMap.RealTime(); - // Get the FairLogger - FairLogger::GetLogger(); - eventNumber = fnStart; return kSUCCESS; From ae13bc5fdf07ac781d3034d963b39e114f94a418 Mon Sep 17 00:00:00 2001 From: Cristovao Vilela Date: Wed, 21 Jan 2026 18:00:55 +0100 Subject: [PATCH 76/99] Add DB authentication --- analysis/makeRunListDB.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/analysis/makeRunListDB.py b/analysis/makeRunListDB.py index 51b6bee8af..4aef4bbc13 100644 --- a/analysis/makeRunListDB.py +++ b/analysis/makeRunListDB.py @@ -8,6 +8,8 @@ parser = argparse.ArgumentParser( prog="makeRunListDB", description="Extracts a list of runs from the SND@LHC DB, matching the conditions given in the arguments. Produces an XML file with the run list and a summary of the selection.") +parser.add_argument("--username", type=str, help="Database username. Contact database maintainer to obtain it", required=True) +parser.add_argument("--password", type=str, help="Database password. Contact database maintainer to obtain it", required=True) parser.add_argument("--name", type=str, help="Run list name", required=True) parser.add_argument("--years", nargs="+", type=int, help="Years to be included, e.g. 2022 2023", required=True) parser.add_argument("--min_events", type=int, help="Minimum number of events in run", default=0) @@ -23,7 +25,8 @@ parser.add_argument("--exclude_runs", nargs="+", type=int, help="Runs to exclude from the list", default=[]) args = parser.parse_args() -client = pymongo.MongoClient("sndrundb.cern.ch") +client = pymongo.MongoClient('sndrundb.cern.ch', username=args.username, password=args.password, authSource='sndrundb', authMechanism='SCRAM-SHA-1') + db = client.sndrundb pipeline = [] From 94cd389f1ed1655ccd85d1eb39cacb584bb2a4a3 Mon Sep 17 00:00:00 2001 From: siilieva <84918585+siilieva@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:07:29 +0100 Subject: [PATCH 77/99] Fix format issue in modifyGeo: affected run_256, scifi5 positions 0.5**u.mrad translates to ~1 rad which is correct given ** is python's power operator Of course, it is catastrophic for positions. Fixed the only two occurrences, but one of them is 0**mrad so the corresponding run_258 was not affected. --- shipLHC/modifyGeoFileDict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index 3a198257c1..86c14c669d 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -668,7 +668,7 @@ def modifyDicts(year=2024): 1.22*u.mrad, 0.00*u.mrad, -0.24*u.mrad, 1.50*u.mrad, 0.10*u.mrad, 0.00*u.mrad, -1.52*u.mrad, -0.21*u.mrad, 0.21*u.mrad, - 1.83*u.mrad, -0.24*u.mrad, 0.50**u.mrad] + 1.83*u.mrad, -0.24*u.mrad, 0.50*u.mrad] alignment['t_12123']=[ #2025 dummy target run_257 and ion run_258 230.00*u.um, 213.75*u.um, 268.00*u.um, -70.75*u.um, 9.00*u.um, -36.25*u.um, @@ -684,7 +684,7 @@ def modifyDicts(year=2024): -0.18*u.mrad, -0.01*u.mrad, 0.23*u.mrad, 0.45*u.mrad, 0.05*u.mrad, 0.00*u.mrad, 0.39*u.mrad, -0.09*u.mrad, 0.01*u.mrad, - -0.59*u.mrad, 0.01*u.mrad, 0.00**u.mrad] + -0.59*u.mrad, 0.01*u.mrad, 0.00*u.mrad] scifi_spatial_aligment_consts = { 2022: ['t_0', 't_4361','t_4575','t_4855','t_5172'], From 51e8d6f3179646af9e4f3f11e6374f6115803c56 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 19 Dec 2025 11:56:11 +0100 Subject: [PATCH 78/99] GSimpleNtpConverter:fix typo --- shipgen/sndlhcGSimpleNtpConverter.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shipgen/sndlhcGSimpleNtpConverter.cxx b/shipgen/sndlhcGSimpleNtpConverter.cxx index 827cb2b584..10adbf2868 100644 --- a/shipgen/sndlhcGSimpleNtpConverter.cxx +++ b/shipgen/sndlhcGSimpleNtpConverter.cxx @@ -252,7 +252,7 @@ int main(int argc, char **argv) meta_entry->auxintname.push_back("Last interaction ID"); meta_entry->auxdblname.push_back("age"); meta_entry->auxdblname.push_back("FLUKA_weight"); - meta_entry->auxdblname.push_back("Kenetic energy"); + meta_entry->auxdblname.push_back("Kinetic energy"); meta_entry->auxdblname.push_back("X coordinate"); meta_entry->auxdblname.push_back("Y coordinate"); meta_entry->auxdblname.push_back("X cosine"); @@ -260,11 +260,11 @@ int main(int argc, char **argv) meta_entry->auxdblname.push_back("Last decay X coordinate"); meta_entry->auxdblname.push_back("Last decay Y coordinate"); meta_entry->auxdblname.push_back("Last decay Z coordinate"); - meta_entry->auxdblname.push_back("Kenetic Energy of decay parent"); + meta_entry->auxdblname.push_back("Kinetic Energy of decay parent"); meta_entry->auxdblname.push_back("Last interaction in Z coordinate"); meta_entry->auxdblname.push_back("Last in interaction Y coordinate"); meta_entry->auxdblname.push_back("Last in interaction Z coordinate"); - meta_entry->auxdblname.push_back("Kenetic energy of parent"); + meta_entry->auxdblname.push_back("Kinetic energy of parent"); metaOut->Fill(); @@ -273,4 +273,4 @@ int main(int argc, char **argv) tOut->Write(); fOut->Close(); std::cout << "Done converting" << std::endl; -} \ No newline at end of file +} From 94cd951b45f305690fda38afac185c15d1e99688 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 19 Dec 2025 11:58:40 +0100 Subject: [PATCH 79/99] Particle gun: add generation run ID --- shipLHC/run_simSND.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/shipLHC/run_simSND.py b/shipLHC/run_simSND.py index 60dbf1b580..45ae972084 100644 --- a/shipLHC/run_simSND.py +++ b/shipLHC/run_simSND.py @@ -33,6 +33,7 @@ parser.add_argument("--Estart", dest="Estart", help="start of energy range of particle gun for muflux detector (default=10 GeV)", required=False, default=10, type=float) parser.add_argument("--Eend", dest="Eend", help="end of energy range of particle gun for muflux detector (default=10 GeV)", required=False, default=10, type=float) +parser.add_argument("--PGrunID", dest="PGrunID",help="PG run ID", required=False, type=int) parser.add_argument("--multiplePGSources", help="Multiple particle guns in a x-y plane at a fixed z or in a 3D volume", action="store_true") parser.add_argument("--EVx", dest="EVx", help="particle gun start xpos", required=False, default=0, type=float) parser.add_argument("--EVy", dest="EVy", help="particle gun start ypos", required=False, default=0, type=float) @@ -173,6 +174,9 @@ def Exec(self,opt): # -----Particle Gun----------------------- if simEngine == "PG": + if not options.PGrunID: + print("Missing option '--PGrunID', which provides PG run ID. Set it and run again!") + exit() myPgun = ROOT.FairBoxGenerator(options.pID,1) myPgun.SetPRange(options.Estart,options.Eend) myPgun.SetPhiRange(0, 360) # // Azimuth angle range [degree] @@ -308,6 +312,12 @@ def Exec(self,opt): # -----Initialize simulation run------------------------------------ run.Init() + +if simEngine == "PG": + # set the runID for + theHeader = run.GetMCEventHeader() + theHeader.SetRunID(options.PGrunID) + gMC = ROOT.TVirtualMC.GetMC() fStack = gMC.GetStack() if MCTracksWithHitsOnly: @@ -407,7 +417,7 @@ def Exec(self,opt): if options.genie == 4 : f_input = ROOT.TFile(inputFile) - gst = f_input.gst + gst = f_input.Get("gst") selection_string = "(Entry$ >= "+str(options.firstEvent)+")" if (options.firstEvent + options.nEvents) < gst.GetEntries() : From 5e5ec6b0808f74c3b9ad8d6cb51696b96fd93b94 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 19 Dec 2025 12:00:34 +0100 Subject: [PATCH 80/99] Record simulation run and event ID For the cases of passing muon, DIS and Genie events. --- shipLHC/muonDis.py | 8 ++++++++ shipgen/GenieGenerator.cxx | 16 +++++++++++++++- shipgen/GenieGenerator.h | 5 +++-- shipgen/MuDISGenerator.cxx | 9 +++++++++ shipgen/MuDISGenerator.h | 7 ++++--- shipgen/NtupleGenerator_FLUKA.cxx | 12 ++++++++++-- shipgen/NtupleGenerator_FLUKA.h | 6 +++--- 7 files changed, 52 insertions(+), 11 deletions(-) diff --git a/shipLHC/muonDis.py b/shipLHC/muonDis.py index 1e8f2ce829..14e55b4580 100755 --- a/shipLHC/muonDis.py +++ b/shipLHC/muonDis.py @@ -461,10 +461,16 @@ def makeMuDISEvents(withElossFunction=False,nucleon='p+'): myPythia.SetMRPY(1,R) mutype = {-13:'gamma/mu+',13:'gamma/mu-'} # DIS event +# run number +# event number # incoming muon, id:px:py:pz:x:y:z:w # outgoing particles, id:px:py:pz + run = array('i', [0]) + event = array('i', [0]) fout = ROOT.TFile('muonDis_'+str(options.run)+'.root','recreate') dTree = ROOT.TTree('DIS','muon DIS') + dTree.Branch("run", run, "run/I") + dTree.Branch("event", event, "event/I") iMuon = ROOT.TClonesArray("TParticle") iMuonBranch = dTree.Branch("InMuon",iMuon,32000,-1) dPart = ROOT.TClonesArray("TParticle") @@ -480,6 +486,8 @@ def makeMuDISEvents(withElossFunction=False,nucleon='p+'): nMade = 0 for k in range(options.nStart,nEnd): rc = sTree.GetEvent(k) + run[0] = int(sTree.run) + event[0] = int(sTree.event) # make n events / muon px,py,pz = sTree.px,sTree.py,sTree.pz x,y,z = sTree.x,sTree.y,sTree.z-SND_Z diff --git a/shipgen/GenieGenerator.cxx b/shipgen/GenieGenerator.cxx index 9420abe41f..e1cdd0ed63 100644 --- a/shipgen/GenieGenerator.cxx +++ b/shipgen/GenieGenerator.cxx @@ -15,6 +15,7 @@ #include "TGeoCompositeShape.h" #include "TParticle.h" #include "TClonesArray.h" +#include "FairMCEventHeader.h" using std::cout; using std::endl; @@ -52,6 +53,8 @@ Bool_t GenieGenerator::Init(const char* fileName, const int firstEvent) { fTree = (TTree *)fInputFile->Get("gst"); fNevents = fTree->GetEntries(); fn = firstEvent; + fTree->SetBranchAddress("FlukaRun",&runN); // run number + fTree->SetBranchAddress("evtNumber",&eventN); // event number fTree->SetBranchAddress("Ev",&Ev); // incoming neutrino energy fTree->SetBranchAddress("pxv",&pxv); fTree->SetBranchAddress("pyv",&pyv); @@ -494,7 +497,12 @@ Bool_t GenieGenerator::ReadEvent(FairPrimaryGenerator* cpg) 0, true); } - + + // Set the generation run and event numbers through the MC event header + FairMCEventHeader* header = cpg->GetEvent(); + header->SetEventID(eventN); + header->SetRunID(runN); + return kTRUE; } else { @@ -706,6 +714,12 @@ Bool_t GenieGenerator::ReadEvent(FairPrimaryGenerator* cpg) //cout << "Info GenieGenerator Return from GenieGenerator" << endl; } } + + // Set the generation run and event numbers through the MC event header + FairMCEventHeader* header = cpg->GetEvent(); + header->SetEventID(eventN); + header->SetRunID(runN); + return kTRUE; } // ------------------------------------------------------------------------- diff --git a/shipgen/GenieGenerator.h b/shipgen/GenieGenerator.h index 35bfb23e0e..b087944791 100644 --- a/shipgen/GenieGenerator.h +++ b/shipgen/GenieGenerator.h @@ -1,5 +1,5 @@ #ifndef PNDGeGENERATOR_H -#define PNDGeGENERATOR_H 1 +#define PNDGeGENERATOR_H 3 #include "TROOT.h" #include "FairGenerator.h" @@ -52,6 +52,7 @@ class GenieGenerator : public FairGenerator private: protected: + int runN,eventN; Double_t FLUKA_x_cos, FLUKA_y_cos, FLUKA_x, FLUKA_y; Double_t fDeltaE_GenieFLUKA_nu; Double_t Yvessel,Xvessel,Lvessel,ztarget,startZ,endZ; @@ -78,7 +79,7 @@ class GenieGenerator : public FairGenerator TH1D* pyslice[3000][500];//! TClonesArray *ancstr;//! - ClassDef(GenieGenerator,2); + ClassDef(GenieGenerator,3); }; #endif /* !PNDGeGENERATOR_H */ diff --git a/shipgen/MuDISGenerator.cxx b/shipgen/MuDISGenerator.cxx index 211bfae8f9..68b790a7aa 100644 --- a/shipgen/MuDISGenerator.cxx +++ b/shipgen/MuDISGenerator.cxx @@ -13,6 +13,7 @@ #include "TVectorD.h" #include "TParticle.h" #include "TGeoCompositeShape.h" +#include "FairMCEventHeader.h" using std::cout; using std::endl; @@ -49,6 +50,8 @@ Bool_t MuDISGenerator::Init(const char* fileName, const int firstEvent) { fTree = (TTree *)fInputFile->Get("DIS"); fNevents = fTree->GetEntries(); fn = firstEvent; + fTree->SetBranchAddress("run",&runN); // run number + fTree->SetBranchAddress("event",&eventN); // event number fTree->SetBranchAddress("InMuon",&iMuon); // incoming muon fTree->SetBranchAddress("Particles",&dPart); return kTRUE; @@ -286,6 +289,12 @@ Bool_t MuDISGenerator::ReadEvent(FairPrimaryGenerator* cpg) TParticle* Part = dynamic_cast(dPart->AddrAt(i)); cpg->AddTrack(Part->GetPdgCode(),Part->Px(),Part->Py(),Part->Pz(),xmu,ymu,zmu,0,true,Part->Energy(),mu->T(),w); } + + // Set the generation run and event numbers through the MC event header + FairMCEventHeader* header = cpg->GetEvent(); + header->SetEventID(eventN); + header->SetRunID(runN); + return kTRUE; } // ------------------------------------------------------------------------- diff --git a/shipgen/MuDISGenerator.h b/shipgen/MuDISGenerator.h index 6d6dd4606e..5d2916f586 100644 --- a/shipgen/MuDISGenerator.h +++ b/shipgen/MuDISGenerator.h @@ -1,5 +1,5 @@ #ifndef PNDMuGENERATOR_H -#define PNDMuGENERATOR_H 1 +#define PNDMuGENERATOR_H 2 #include "TROOT.h" #include "FairGenerator.h" @@ -40,13 +40,14 @@ class MuDISGenerator : public FairGenerator protected: Double_t startZ,endZ; TClonesArray* iMuon ; - TClonesArray* dPart ; + TClonesArray* dPart ; + int runN, eventN; FairLogger* fLogger; //! don't make it persistent, magic ROOT command TFile* fInputFile; TTree* fTree; int fNevents; int fn; bool fFirst; - ClassDef(MuDISGenerator,1); + ClassDef(MuDISGenerator,2); }; #endif /* !PNDMuGENERATOR_H */ diff --git a/shipgen/NtupleGenerator_FLUKA.cxx b/shipgen/NtupleGenerator_FLUKA.cxx index e01cd2bc8a..404f6ba531 100644 --- a/shipgen/NtupleGenerator_FLUKA.cxx +++ b/shipgen/NtupleGenerator_FLUKA.cxx @@ -5,6 +5,7 @@ #include "FairPrimaryGenerator.h" #include "NtupleGenerator_FLUKA.h" #include "FairLogger.h" +#include "FairMCEventHeader.h" // read events from ntuples produced by FLUKA @@ -39,6 +40,8 @@ Bool_t NtupleGenerator_FLUKA::Init(const char* fileName, const int firstEvent) { fTree->SetBranchAddress("primaries",&primaries); } else { + fTree->SetBranchAddress("run",&runN); // run number + fTree->SetBranchAddress("event",&eventN); // event number fTree->SetBranchAddress("id",&id); // particle id fTree->SetBranchAddress("generation",&generation); // origin generation number fTree->SetBranchAddress("t",&t); // time of flight @@ -83,7 +86,7 @@ Bool_t NtupleGenerator_FLUKA::ReadEvent(FairPrimaryGenerator* cpg) for(int i=0; i(primaries->AddrAt(i)); - // what to do with generation info? convert time from ns to sec + //convert time from ns to sec cpg->AddTrack(int(primary->id),primary->px,primary->py,primary->pz, primary->x,primary->y,primary->z-SND_Z,-1,true,primary->E, primary->t/1E9,primary->w,(TMCProcess)primary->generation); @@ -94,11 +97,16 @@ Bool_t NtupleGenerator_FLUKA::ReadEvent(FairPrimaryGenerator* cpg) } } else { - // what to do with generation info? convert time from ns to sec + // convert time from ns to sec cpg->AddTrack(int(id[0]),px[0],py[0],pz[0],x[0],y[0],z[0]-SND_Z,-1,true,E[0],t[0]/1E9,w[0],(TMCProcess)generation[0]); LOG(DEBUG) << "NtupleGenerator_FLUKA: add muon " << id[0]<<","<GetEvent(); + header->SetEventID(int(eventN)); + header->SetRunID(int(runN)); + return kTRUE; } diff --git a/shipgen/NtupleGenerator_FLUKA.h b/shipgen/NtupleGenerator_FLUKA.h index f7756d287b..fd1852bce1 100644 --- a/shipgen/NtupleGenerator_FLUKA.h +++ b/shipgen/NtupleGenerator_FLUKA.h @@ -1,5 +1,5 @@ #ifndef FLUKAntGENERATOR_H -#define FLUKAntGENERATOR_H 1 +#define FLUKAntGENERATOR_H 4 #include "TROOT.h" #include "FairGenerator.h" @@ -32,13 +32,13 @@ class NtupleGenerator_FLUKA : public FairGenerator private: protected: - Double_t id[1],generation[1],E[1],t[1],px[1],py[1],pz[1],x[1],y[1],z[1],w[1],SND_Z; + Double_t runN,eventN,id[1],generation[1],E[1],t[1],px[1],py[1],pz[1],x[1],y[1],z[1],w[1],SND_Z; TFile* fInputFile; TTree* fTree; int fNevents; int fn; TClonesArray* primaries; - ClassDef(NtupleGenerator_FLUKA,3); + ClassDef(NtupleGenerator_FLUKA,4); }; #endif /* !FLUKAntGENERATOR_H */ From 27dd50f7541d82c7182d149d4f71100e741ab2ae Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 19 Dec 2025 12:48:43 +0100 Subject: [PATCH 81/99] Digi task: pass header info to digi file Once again need to bypass FairROOT branch management. --- sndFairTasks/DigiTaskSND.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sndFairTasks/DigiTaskSND.cxx b/sndFairTasks/DigiTaskSND.cxx index 61c8b0ee3c..df442c844f 100644 --- a/sndFairTasks/DigiTaskSND.cxx +++ b/sndFairTasks/DigiTaskSND.cxx @@ -62,10 +62,10 @@ InitStatus DigiTaskSND::Init() if ( fMCEventHeader == nullptr ) { fMCEventHeader = static_cast(gROOT->FindObjectAny("MCEventHeader.")); } - if ( fMCEventHeader == nullptr ) { - ioman->GetInTree()->SetBranchAddress("MCEventHeader.", &fMCEventHeader); - LOG(INFO) << "MCEventHeader. branch is found"; - } + // To ensure the header is updated per newly read event. + ioman->GetInTree()->SetBranchAddress("MCEventHeader.", &fMCEventHeader); + LOG(INFO) << "MCEventHeader. branch is found"; + // Get input MC points fScifiPointArray = static_cast(ioman->GetObject("ScifiPoint")); fvetoPointArray = static_cast(ioman->GetObject("vetoPoint")); From df212cd368f7310c532a3214f63a494f01e82430 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 19 Dec 2025 13:30:26 +0100 Subject: [PATCH 82/99] Digi cpp task: Remove redundant forceful exit --- shipLHC/run_digiSND.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/shipLHC/run_digiSND.py b/shipLHC/run_digiSND.py index 128e63e1f7..11dedfe681 100644 --- a/shipLHC/run_digiSND.py +++ b/shipLHC/run_digiSND.py @@ -145,5 +145,3 @@ def mem_monitor(): ctime = timer.CpuTime() print(' ') print("Real time ",rtime, " s, CPU time ",ctime,"s") -if options.FairTask_digi: - atexit.register(pyExit) From 70c43d14a1a8ed518e7ad0fa5398ca300336ce4e Mon Sep 17 00:00:00 2001 From: fmei Date: Mon, 9 Mar 2026 18:10:45 +0100 Subject: [PATCH 83/99] use GetImpactXpos from sndsw --- analysis/tools/sndUSPlane.cxx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index 8b484e7b76..ef39f17c8c 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -10,7 +10,7 @@ #include "MuFilterHit.h" #include "ShipUnit.h" -snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool use_small_sipms_sipms) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) +snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool use_small_sipms) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""),std::nan("")), station_(station) { for ( auto mu_hit : snd_hits) { @@ -26,7 +26,7 @@ snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const hit.is_large = !mu_hit->isShort(i); hit.is_right = i > 7 ? true : false; - if (!hit.is_large && !use_small_sipms_sipms) + if (!hit.is_large && !use_small_sipms) { hit.timestamp = std::nan(""); hit.qdc = std::nan(""); @@ -39,8 +39,8 @@ snd::analysis_tools::USPlane::USPlane(std::vector snd_hits, const hit.timestamp = configuration_.is_mc ? mu_hit->GetTime(i) / ShipUnit::snd_TDC2ns : mu_hit->GetTime(i); hit.qdc = mu_hit->GetSignal(i); // use the left and right measurements to calculate the x coordinate along the bar - float timeConversion = configuration_.is_mc ? 1. : ShipUnit::snd_TDC2ns; - hit.x = A.X() - 0.5*(mu_hit->GetDeltaT()*timeConversion*configuration_.us_signal_speed+configuration_.us_bar_length); + float tmp_x = mu_hit->GetImpactXpos(true, true, false, configuration_.is_mc); + hit.x = (tmp_x < -990.) ? std::nan("") : A.X() - tmp_x; hit.y = A.Y(); hit.z = A.Z(); } @@ -56,13 +56,12 @@ void snd::analysis_tools::USPlane::FindCentroid() cleaned_hits.erase(std::remove_if(cleaned_hits.begin(), cleaned_hits.end(), [&](auto &hit) - { return (hit.qdc <= 0 || hit.is_large == false); }), + { return (hit.qdc <= 0 || hit.is_large == false || std::isnan(hit.x)); }), cleaned_hits.end()); // min number of hit in the plane to attempt to find a centroid if (static_cast(cleaned_hits.size()) < configuration_.us_min_n_hits_for_centroid) { - // std::cout<<"Not enough hits in US plane " << station_ <<" to find centroid\n"; return; } From feaef40f057cffcfc7afd4e156fdb2572f641547 Mon Sep 17 00:00:00 2001 From: fmei Date: Mon, 9 Mar 2026 18:10:45 +0100 Subject: [PATCH 84/99] add veto and ds plane classes --- analysis/tools/AnalysisToolsLinkDef.h | 6 +- analysis/tools/CMakeLists.txt | 2 + analysis/tools/sndConfiguration.cxx | 3 + analysis/tools/sndConfiguration.h | 9 ++- analysis/tools/sndDSPlane.cxx | 83 ++++++++++++++++++++++++ analysis/tools/sndDSPlane.h | 62 ++++++++++++++++++ analysis/tools/sndPlaneTools.cxx | 57 ++++++++++++++++- analysis/tools/sndPlaneTools.h | 6 +- analysis/tools/sndScifiPlane.cxx | 6 ++ analysis/tools/sndScifiPlane.h | 2 + analysis/tools/sndUSPlane.cxx | 7 ++- analysis/tools/sndUSPlane.h | 2 + analysis/tools/sndVetoPlane.cxx | 90 +++++++++++++++++++++++++++ analysis/tools/sndVetoPlane.h | 63 +++++++++++++++++++ 14 files changed, 389 insertions(+), 9 deletions(-) create mode 100644 analysis/tools/sndDSPlane.cxx create mode 100644 analysis/tools/sndDSPlane.h create mode 100644 analysis/tools/sndVetoPlane.cxx create mode 100644 analysis/tools/sndVetoPlane.h diff --git a/analysis/tools/AnalysisToolsLinkDef.h b/analysis/tools/AnalysisToolsLinkDef.h index 0b2e56ead1..36d9b7250e 100644 --- a/analysis/tools/AnalysisToolsLinkDef.h +++ b/analysis/tools/AnalysisToolsLinkDef.h @@ -8,8 +8,10 @@ #pragma link C++ nestedtypedef; #pragma link C++ class snd::Configuration+; +#pragma link C++ class snd::analysis_tools::VetoPlane+; #pragma link C++ class snd::analysis_tools::ScifiPlane+; #pragma link C++ class snd::analysis_tools::USPlane+; +#pragma link C++ class snd::analysis_tools::DSPlane+; #pragma link C++ namespace snd::analysis_tools; #pragma link C++ defined_in namespace snd::analysis_tools; @@ -38,8 +40,10 @@ #pragma link C++ function snd::analysis_tools::GetGeoPath(int, std::string); #pragma link C++ function snd::analysis_tools::GetGeometry(int, const std::string &); #pragma link C++ function snd::analysis_tools::GetGeometry(const std::string &); +#pragma link C++ function snd::analysis_tools::FillVeto(const snd::Configuration &, TClonesArray *, MuFilter *); #pragma link C++ function snd::analysis_tools::FillScifi(const snd::Configuration &, TClonesArray *, Scifi *); -#pragma link C++ function snd::analysis_tools::FillUS(const snd::Configuration &, TClonesArray *, MuFilter *); +#pragma link C++ function snd::analysis_tools::FillUS(const snd::Configuration &, TClonesArray *, MuFilter *, bool); +#pragma link C++ function snd::analysis_tools::FillDS(const snd::Configuration &, TClonesArray *, MuFilter *); #pragma link C++ function snd::analysis_tools::GetScifiShowerStart(const std::vector &); diff --git a/analysis/tools/CMakeLists.txt b/analysis/tools/CMakeLists.txt index e0910586d2..6f7e12556e 100644 --- a/analysis/tools/CMakeLists.txt +++ b/analysis/tools/CMakeLists.txt @@ -14,8 +14,10 @@ sndSciFiTools.cxx sndTchainGetter.cxx sndGeometryGetter.cxx sndConfiguration.cxx +sndVetoPlane.cxx sndScifiPlane.cxx sndUSPlane.cxx +sndDSPlane.cxx sndPlaneTools.cxx sndShowerTools.cxx ) diff --git a/analysis/tools/sndConfiguration.cxx b/analysis/tools/sndConfiguration.cxx index 9f4a3badf1..398e44cffc 100644 --- a/analysis/tools/sndConfiguration.cxx +++ b/analysis/tools/sndConfiguration.cxx @@ -19,6 +19,7 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter scifi_centroid_error_z = scifi_geometry->GetConfParF("Scifi/channel_height"); veto_n_stations = muon_filter_geometry->GetConfParI("MuFilter/NVetoPlanes"); + veto_bar_per_station = muon_filter_geometry->GetConfParI("MuFilter/NVetoBars"); us_n_stations = muon_filter_geometry->GetConfParI("MuFilter/NUpstreamPlanes"); us_bar_per_station = muon_filter_geometry->GetConfParI("MuFilter/NUpstreamBars"); @@ -31,6 +32,7 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter us_centroid_error_z = muon_filter_geometry->GetConfParF("MuFilter/UpstreamBarZ"); ds_n_stations = muon_filter_geometry->GetConfParI("MuFilter/NDownstreamPlanes"); + ds_bar_per_station = muon_filter_geometry->GetConfParI("MuFilter/NDownstreamBars"); ds_hor_spatial_resolution_x = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarX") / std::sqrt(12); ds_hor_spatial_resolution_y = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarY") / std::sqrt(12); ds_hor_spatial_resolution_z = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarZ") / std::sqrt(12); @@ -39,6 +41,7 @@ snd::Configuration::Configuration(Option option, Scifi *scifi_geometry, MuFilter ds_ver_spatial_resolution_z = muon_filter_geometry->GetConfParF("MuFilter/DownstreamBarZ_ver") / std::sqrt(12); // Common parameters not present in geometry + veto_min_hit_on_bar = 0; scifi_min_timestamp = std::nan(""); scifi_max_timestamp = std::nan(""); us_min_timestamp = std::nan(""); diff --git a/analysis/tools/sndConfiguration.h b/analysis/tools/sndConfiguration.h index eb6282dbd0..6496adabaa 100644 --- a/analysis/tools/sndConfiguration.h +++ b/analysis/tools/sndConfiguration.h @@ -44,6 +44,9 @@ namespace snd { double us_z_min; double us_z_max; + double us_bar_length; + double us_signal_speed; + double ds_hor_spatial_resolution_x; double ds_hor_spatial_resolution_y; double ds_hor_spatial_resolution_z; @@ -52,6 +55,8 @@ namespace snd { double ds_ver_spatial_resolution_z; int veto_n_stations; + int veto_bar_per_station; + int veto_min_hit_on_bar; int scifi_n_stations; int scifi_n_channels_per_plane; @@ -67,12 +72,10 @@ namespace snd { int us_min_n_hits_for_centroid; int us_min_hit_on_bar; - double us_bar_length; - double us_signal_speed; - int centroid_min_valid_station; int ds_n_stations; + int ds_bar_per_station; bool is_mc; diff --git a/analysis/tools/sndDSPlane.cxx b/analysis/tools/sndDSPlane.cxx new file mode 100644 index 0000000000..8b8fa78ff4 --- /dev/null +++ b/analysis/tools/sndDSPlane.cxx @@ -0,0 +1,83 @@ +#include "sndDSPlane.h" + +#include +#include +#include +#include + +#include "MuFilter.h" +#include "MuFilterHit.h" +#include "ShipUnit.h" +#include "FairLogger.h" + +snd::analysis_tools::DSPlane::DSPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station) : configuration_(configuration), station_(station) +{ + for ( auto mu_hit : snd_hits) + { + TVector3 A, B; + int detectorID = mu_hit->GetDetectorID(); + muon_filter_geometry->GetPosition(detectorID, A, B); + const int n_sipms = muon_filter_geometry->GetnSiPMs(detectorID); + const int n_sides = muon_filter_geometry->GetnSides(detectorID); + for (int i{0}; i < n_sipms * n_sides; ++i) + { + if (mu_hit->isMasked(i) || mu_hit->GetSignal(i) < -990.) continue; + DSHit hit; + hit.bar = static_cast(detectorID % 1000); + hit.channel_index = n_sipms * n_sides * hit.bar + i; + hit.is_right = (i >= n_sipms) ? true : false; + hit.timestamp = configuration_.is_mc ? mu_hit->GetTime(i) / ShipUnit::snd_TDC2ns : mu_hit->GetTime(i); + hit.qdc = mu_hit->GetSignal(i); + // use the left and right measurements to calculate the x coordinate along the bar + if (!mu_hit->isVertical()) { + hit.is_x = false; + float tmp_x = mu_hit->GetImpactXpos(true, true, false, configuration_.is_mc); + hit.x = (tmp_x < -990.) ? std::nan("") : A.X() - tmp_x; + hit.y = A.Y(); + } + else { + hit.is_x = true; + hit.x = A.X(); + hit.y = std::nan(""); + } + hit.z = A.Z(); + hits_.push_back(hit); + } + } +} + +const double snd::analysis_tools::DSPlane::GetTotQdc() const +{ + double tot_qdc = std::accumulate(hits_.begin(), hits_.end(), 0.0, + [](double sum, const auto &b) {return sum + b.qdc;}); + return tot_qdc; +} + +const int snd::analysis_tools::DSPlane::GetBarNHits(int bar_to_compute) const +{ + int bar_hit = std::count_if(hits_.begin(), hits_.end(), + [bar_to_compute](const auto &hit) {return hit.bar == bar_to_compute;}); + return bar_hit; +} + +void snd::analysis_tools::DSPlane::TimeFilter(double min_timestamp, double max_timestamp) +{ + hits_.erase(std::remove_if(hits_.begin(), hits_.end(), + [&](auto &hit) + { return hit.timestamp < min_timestamp || hit.timestamp > max_timestamp; }), + hits_.end()); +} + +const int snd::analysis_tools::DSPlane::GetNHitBars() const +{ + int count{0}; + for (int bar{0}; bar < 2*configuration_.ds_bar_per_station; ++bar) { + if (GetBarNHits(bar) > 0) count++; + } + return count; +} + +void snd::analysis_tools::DSPlane::DSHit::Print() const +{ + LOGF(INFO, "DSHit ch_idx :%d\tposition: (%f,%f,%f)\ttime: %f\tqdc: %f\tbar: %d\tis_x: %d\tis_right: %d", channel_index, x, y, z, timestamp, qdc, bar, is_x, is_right); +} \ No newline at end of file diff --git a/analysis/tools/sndDSPlane.h b/analysis/tools/sndDSPlane.h new file mode 100644 index 0000000000..f658f577e0 --- /dev/null +++ b/analysis/tools/sndDSPlane.h @@ -0,0 +1,62 @@ +#ifndef SND_DSPLANE_H +#define SND_DSPLANE_H + +#include + +#include "MuFilter.h" +#include "MuFilterHit.h" +#include "sndConfiguration.h" + +namespace snd { + namespace analysis_tools { + class DSPlane + { + public: + + // right and left side of DS bars + template + struct rl_pair + { + T right{}; + T left{}; + }; + + // hits vector, each hit has info about timestamp, qdc and position + struct DSHit + { + int channel_index; + int bar; + + double qdc; + double timestamp; + double x; + double y; + double z; + + bool is_x; // true if vertical (measures x) + bool is_right; + + void Print() const; + }; + + DSPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station); + + const int GetNHits() const { return hits_.size(); }; + const int GetStation() const { return station_; } + + const double GetTotQdc() const; + const std::vector GetHits() const { return hits_; }; + const int GetBarNHits(int bar_to_compute) const; + + void TimeFilter(double min_timestamp, double max_timestamp); + const int GetNHitBars() const; + + private: + std::vector hits_; + Configuration configuration_; + int station_; + }; + } +} + +#endif diff --git a/analysis/tools/sndPlaneTools.cxx b/analysis/tools/sndPlaneTools.cxx index 6bd3f118c1..0dbbe0b0ff 100644 --- a/analysis/tools/sndPlaneTools.cxx +++ b/analysis/tools/sndPlaneTools.cxx @@ -1,21 +1,48 @@ #include "sndPlaneTools.h" #include +#include #include "TClonesArray.h" #include "Scifi.h" #include "MuFilter.h" #include "sndConfiguration.h" +#include "sndVetoPlane.h" #include "sndScifiPlane.h" #include "sndUSPlane.h" +#include "sndDSPlane.h" #include "sndScifiHit.h" #include "MuFilterHit.h" +std::vector snd::analysis_tools::FillVeto(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry) +{ + + std::vector veto_planes; + const int n_mufi_hits{mufi_hits->GetEntries()}; + + const int n_station = configuration.veto_n_stations; + std::vector> plane_hits(n_station); + + for (int i{0}; i < n_mufi_hits; ++i) { + auto hit = static_cast(mufi_hits->At(i)); + if (hit->GetSystem()!=1) continue; + int station_id = hit->GetPlane(); + if (station_id > -1 && station_id < n_station) { + plane_hits[station_id].push_back(hit); + } + else throw std::runtime_error{"Invalid Veto plane"}; + } + for (int st{0}; st < n_station; ++st) { + veto_planes.emplace_back(snd::analysis_tools::VetoPlane(plane_hits[st], configuration, mufilter_geometry, st+1)); + } + return veto_planes; +} + std::vector snd::analysis_tools::FillScifi(const snd::Configuration &configuration, TClonesArray *sf_hits, Scifi *scifi_geometry) { std::vector scifi_planes; - int n_sf_hits{sf_hits->GetEntries()}; + const int n_sf_hits{sf_hits->GetEntries()}; const int max_station = configuration.scifi_n_stations; std::vector> stations_hits(max_station); @@ -40,7 +67,7 @@ std::vector snd::analysis_tools::FillUS(const snd: { std::vector us_planes; - int n_mufi_hits{mufi_hits->GetEntries()}; + const int n_mufi_hits{mufi_hits->GetEntries()}; const int n_station = configuration.us_n_stations; std::vector> plane_hits(n_station); @@ -52,10 +79,34 @@ std::vector snd::analysis_tools::FillUS(const snd: if (station_id > -1 && station_id < n_station) { plane_hits[station_id].push_back(hit); } - else throw std::runtime_error{"Invalid US plane"}; + else throw std::runtime_error{"Invalid US plane"}; } for (int st{0}; st < n_station; ++st) { us_planes.emplace_back(snd::analysis_tools::USPlane(plane_hits[st], configuration, mufilter_geometry, st+1, use_small_sipms)); } return us_planes; } + +std::vector snd::analysis_tools::FillDS(const snd::Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry) +{ + + std::vector ds_planes; + const int n_mufi_hits{mufi_hits->GetEntries()}; + + const int n_station = configuration.ds_n_stations; + std::vector> plane_hits(n_station); + + for (int i{0}; i < n_mufi_hits; ++i) { + auto hit = static_cast(mufi_hits->At(i)); + if (hit->GetSystem()!=3) continue; + int station_id = hit->GetPlane(); + if (station_id > -1 && station_id < n_station) { + plane_hits[station_id].push_back(hit); + } + else throw std::runtime_error{"Invalid DS plane"}; + } + for (int st{0}; st < n_station; ++st) { + ds_planes.emplace_back(snd::analysis_tools::DSPlane(plane_hits[st], configuration, mufilter_geometry, st+1)); + } + return ds_planes; +} diff --git a/analysis/tools/sndPlaneTools.h b/analysis/tools/sndPlaneTools.h index f442a79503..25e3ac7f78 100644 --- a/analysis/tools/sndPlaneTools.h +++ b/analysis/tools/sndPlaneTools.h @@ -7,14 +7,18 @@ #include "Scifi.h" #include "MuFilter.h" #include "sndConfiguration.h" +#include "sndVetoPlane.h" #include "sndScifiPlane.h" #include "sndUSPlane.h" +#include "sndDSPlane.h" namespace snd { namespace analysis_tools { - // Produce scifi and us planes from data + // Produce veto, scifi, us and ds planes from data + std::vector FillVeto(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry); std::vector FillScifi(const Configuration &configuration, TClonesArray *sf_hits, Scifi *scifi_geometry); std::vector FillUS(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry, bool use_small_sipms=false); + std::vector FillDS(const Configuration &configuration, TClonesArray *mufi_hits, MuFilter *mufilter_geometry); } } diff --git a/analysis/tools/sndScifiPlane.cxx b/analysis/tools/sndScifiPlane.cxx index f0569b57a7..311eb22609 100644 --- a/analysis/tools/sndScifiPlane.cxx +++ b/analysis/tools/sndScifiPlane.cxx @@ -11,6 +11,7 @@ #include "sndScifiHit.h" #include "ShipUnit.h" #include "Math/Point3D.h" +#include "FairLogger.h" snd::analysis_tools::ScifiPlane::ScifiPlane(std::vector snd_hits, const Configuration &configuration, Scifi *scifi_geometry, int station) : configuration_(configuration), centroid_(std::nan(""), std::nan(""), std::nan("")), centroid_error_(std::nan(""), std::nan(""), std::nan("")), station_(station) { @@ -294,3 +295,8 @@ const snd::analysis_tools::ScifiPlane::xy_pair snd::analysis_tools::Scif return energy; } + +void snd::analysis_tools::ScifiPlane::ScifiHit::Print() const +{ + LOGF(INFO, "ScifiHit ch_idx :%d\tposition: (%f,%f,%f)\ttime: %f\tqdc: %f\tis_x: %d", channel_index, x, y, z, timestamp, qdc, is_x); +} \ No newline at end of file diff --git a/analysis/tools/sndScifiPlane.h b/analysis/tools/sndScifiPlane.h index 9994f1f167..2867344eee 100644 --- a/analysis/tools/sndScifiPlane.h +++ b/analysis/tools/sndScifiPlane.h @@ -30,6 +30,8 @@ namespace snd { double z{}; int channel_index{}; bool is_x{}; + + void Print() const; }; ScifiPlane(std::vector snd_hits, const Configuration &configuration, Scifi *scifi_geometry, int station); diff --git a/analysis/tools/sndUSPlane.cxx b/analysis/tools/sndUSPlane.cxx index ef39f17c8c..b3e29a091a 100644 --- a/analysis/tools/sndUSPlane.cxx +++ b/analysis/tools/sndUSPlane.cxx @@ -187,7 +187,8 @@ const snd::analysis_tools::USPlane::sl_pair snd::analysis_tools::USPlane::G return counts; } -const int snd::analysis_tools::USPlane::GetNHitBars() const{ +const int snd::analysis_tools::USPlane::GetNHitBars() const +{ int count{0}; for (int bar{0}; bar < configuration_.us_bar_per_station; ++bar) { if (GetBarNHits(bar).large > configuration_.us_min_hit_on_bar) count++; @@ -195,3 +196,7 @@ const int snd::analysis_tools::USPlane::GetNHitBars() const{ return count; } +void snd::analysis_tools::USPlane::USHit::Print() const +{ + LOGF(INFO, "USHit ch_idx :%d\tposition: (%f,%f,%f)\ttime: %f\tqdc: %f\tbar: %d\tis_right: %d\tis_large: %d", channel_index, x, y, z, timestamp, qdc, bar, is_right, is_large); +} \ No newline at end of file diff --git a/analysis/tools/sndUSPlane.h b/analysis/tools/sndUSPlane.h index df026e2d3d..12261666e3 100644 --- a/analysis/tools/sndUSPlane.h +++ b/analysis/tools/sndUSPlane.h @@ -43,6 +43,8 @@ namespace snd { bool is_large; bool is_right; + + void Print() const; }; USPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station, bool use_small_sipms=false); diff --git a/analysis/tools/sndVetoPlane.cxx b/analysis/tools/sndVetoPlane.cxx new file mode 100644 index 0000000000..ba0509e705 --- /dev/null +++ b/analysis/tools/sndVetoPlane.cxx @@ -0,0 +1,90 @@ +#include "sndVetoPlane.h" + +#include +#include +#include +#include + +#include "MuFilter.h" +#include "MuFilterHit.h" +#include "ShipUnit.h" +#include "FairLogger.h" + +snd::analysis_tools::VetoPlane::VetoPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station) : configuration_(configuration), station_(station) +{ + for ( auto mu_hit : snd_hits) + { + TVector3 A, B; + int detectorID = mu_hit->GetDetectorID(); + muon_filter_geometry->GetPosition(detectorID, A, B); + const int n_sipms = muon_filter_geometry->GetnSiPMs(detectorID); + const int n_sides = muon_filter_geometry->GetnSides(detectorID); + for (int i{0}; i < n_sipms * n_sides; ++i) + { + if (mu_hit->isMasked(i) || mu_hit->GetSignal(i) < -990.) continue; + VetoHit hit; + hit.bar = static_cast(detectorID % 1000); + hit.channel_index = n_sipms * n_sides * hit.bar + i; + hit.is_right = (i >= n_sipms) ? true : false; + hit.timestamp = configuration_.is_mc ? mu_hit->GetTime(i) / ShipUnit::snd_TDC2ns : mu_hit->GetTime(i); + hit.qdc = mu_hit->GetSignal(i); + // use the left and right measurements to calculate the x coordinate along the bar + if (!mu_hit->isVertical()) { + hit.is_x = false; + float tmp_x = mu_hit->GetImpactXpos(true, true, false, configuration_.is_mc); + hit.x = (tmp_x < -990.) ? std::nan("") : A.X() - tmp_x; + hit.y = A.Y(); + } + else { + hit.is_x = true; + hit.x = A.X(); + hit.y = std::nan(""); + } + hit.z = A.Z(); + hits_.push_back(hit); + } + } +} + +const double snd::analysis_tools::VetoPlane::GetTotQdc() const +{ + double tot_qdc = std::accumulate(hits_.begin(), hits_.end(), 0.0, + [](double sum, const auto &b) {return sum + b.qdc;}); + return tot_qdc; +} + +const double snd::analysis_tools::VetoPlane::GetBarQdc(int bar_to_compute) const +{ + double bar_qdc = std::accumulate(hits_.begin(), hits_.end(), 0.0, + [bar_to_compute](double sum, const auto &b) {return (b.bar == bar_to_compute) ? sum + b.qdc : sum;}); + return bar_qdc; +} + +const int snd::analysis_tools::VetoPlane::GetBarNHits(int bar_to_compute) const +{ + int bar_hit = std::count_if(hits_.begin(), hits_.end(), + [bar_to_compute](const auto &hit) {return hit.bar == bar_to_compute;}); + return bar_hit; +} + +void snd::analysis_tools::VetoPlane::TimeFilter(double min_timestamp, double max_timestamp) +{ + hits_.erase(std::remove_if(hits_.begin(), hits_.end(), + [&](auto &hit) + { return hit.timestamp < min_timestamp || hit.timestamp > max_timestamp; }), + hits_.end()); +} + +const int snd::analysis_tools::VetoPlane::GetNHitBars() const +{ + int count{0}; + for (int bar{0}; bar < configuration_.veto_bar_per_station; ++bar) { + if (GetBarNHits(bar) > configuration_.veto_min_hit_on_bar) count++; + } + return count; +} + +void snd::analysis_tools::VetoPlane::VetoHit::Print() const +{ + LOGF(INFO, "VetoHit ch_idx :%d\tposition: (%f,%f,%f)\ttime: %f\tqdc: %f\tbar: %d\tis_x: %d\tis_right: %d", channel_index, x, y, z, timestamp, qdc, bar, is_x, is_right); +} \ No newline at end of file diff --git a/analysis/tools/sndVetoPlane.h b/analysis/tools/sndVetoPlane.h new file mode 100644 index 0000000000..eaa765dec7 --- /dev/null +++ b/analysis/tools/sndVetoPlane.h @@ -0,0 +1,63 @@ +#ifndef SND_VETOPLANE_H +#define SND_VETOPLANE_H + +#include + +#include "MuFilter.h" +#include "MuFilterHit.h" +#include "sndConfiguration.h" + +namespace snd { + namespace analysis_tools { + class VetoPlane + { + public: + + // right and left side of Veto bars + template + struct rl_pair + { + T right{}; + T left{}; + }; + + // hits vector, each hit has info about timestamp, qdc and position + struct VetoHit + { + int channel_index; + int bar; + + double qdc; + double timestamp; + double x; + double y; + double z; + + bool is_x; // true if vertical (measures x) + bool is_right; + + void Print() const; + }; + + VetoPlane(std::vector snd_hits, const Configuration &configuration, MuFilter *muon_filter_geometry, int station); + + const int GetNHits() const { return hits_.size(); }; + const int GetStation() const { return station_; } + + const double GetTotQdc() const; + const double GetBarQdc(int bar_to_compute) const; + const int GetBarNHits(int bar_to_compute) const; + const std::vector GetHits() const { return hits_; }; + const int GetNHitBars() const; + + void TimeFilter(double min_timestamp, double max_timestamp); + + private: + std::vector hits_; + Configuration configuration_; + int station_; + }; + } +} + +#endif From 0594d13e239036a2f4116d4d8d8ec41898826625 Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 3 Mar 2026 19:07:37 +0100 Subject: [PATCH 85/99] Geant4 physics list: change to FTFP_BERT_HP_EMZ This is the list advised by G4 manual, also recently employed in FairShip. --- gconfig/g4Config.C | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gconfig/g4Config.C b/gconfig/g4Config.C index d698dd305f..912404603d 100755 --- a/gconfig/g4Config.C +++ b/gconfig/g4Config.C @@ -25,7 +25,7 @@ void Config() /// When more than one options are selected, they should be separated with '+' /// character: eg. stepLimit+specialCuts. TG4RunConfiguration* runConfiguration - = new TG4RunConfiguration("geomRoot", "QGSP_BERT_HP_PEN", "stepLimiter+specialCuts+specialControls"); + = new TG4RunConfiguration("geomRoot", "FTFP_BERT_HP_EMZ", "stepLimiter+specialCuts+specialControls"); /// Create the G4 VMC TGeant4* geant4 = new TGeant4("TGeant4", "The Geant4 Monte Carlo", runConfiguration); From 594b25a888532466f30b5359740be7f86b92df1f Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 3 Mar 2026 19:08:31 +0100 Subject: [PATCH 86/99] Geant4: remove the 1MeV particle production threshold! --- gconfig/SetCuts.C | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/gconfig/SetCuts.C b/gconfig/SetCuts.C index d59b6ebc24..8b97e2ccc8 100755 --- a/gconfig/SetCuts.C +++ b/gconfig/SetCuts.C @@ -1,6 +1,8 @@ /** Configuration macro for setting common cuts and processes for G3, G4 and Fluka (M. Al-Turany 27.03.2008) specific cuts and processes to g3 or g4 should be set in the g3Config.C, g4Config.C or flConfig.C + Comment out all cuts defined in this macro (S. Ilieva 3.03.2026) + essentially removing the 1 MeV particle production cut-off for SND@LHC G4 simulations */ @@ -33,20 +35,22 @@ void SetCuts() gMC->SetProcess("LOSS",1); /**energy loss*/ gMC->SetProcess("MULS",1); /**multiple scattering*/ - Double_t cut1 = 1.0E-3; // GeV --> 1 MeV - Double_t cutb = 1.0E4; // GeV --> 10 TeV - Double_t tofmax = 1.E10; // seconds - cout << "SetCuts Macro: Setting cuts.." <SetCut("CUTGAM",cut1); /** gammas (GeV)*/ - gMC->SetCut("CUTELE",cut1); /** electrons (GeV)*/ - gMC->SetCut("CUTNEU",cut1); /** neutral hadrons (GeV)*/ - gMC->SetCut("CUTHAD",cut1); /** charged hadrons (GeV)*/ - gMC->SetCut("CUTMUO",cut1); /** muons (GeV)*/ - gMC->SetCut("BCUTE",cut1); /** electron bremsstrahlung (GeV)*/ - gMC->SetCut("BCUTM",cut1); /** muon and hadron bremsstrahlung(GeV)*/ - gMC->SetCut("DCUTE",cut1); /** delta-rays by electrons (GeV)*/ - gMC->SetCut("DCUTM",cut1); /** delta-rays by muons (GeV)*/ - gMC->SetCut("PPCUTM",cut1); /** direct pair production by muons (GeV)*/ - gMC->SetCut("TOFMAX",tofmax); /**time of flight cut in seconds*/ + cout << "SetCuts Macro: No cuts to be set in here." < 1 MeV + //Double_t cutb = 1.0E4; // GeV --> 10 TeV + //Double_t tofmax = 1.E10; // seconds + //cout << "SetCuts Macro: Setting cuts.." <SetCut("CUTGAM",cut1); /** gammas (GeV)*/ + //gMC->SetCut("CUTELE",cut1); /** electrons (GeV)*/ + //gMC->SetCut("CUTNEU",cut1); /** neutral hadrons (GeV)*/ + //gMC->SetCut("CUTHAD",cut1); /** charged hadrons (GeV)*/ + //gMC->SetCut("CUTMUO",cut1); /** muons (GeV)*/ + //gMC->SetCut("BCUTE",cut1); /** electron bremsstrahlung (GeV)*/ + //gMC->SetCut("BCUTM",cut1); /** muon and hadron bremsstrahlung(GeV)*/ + //gMC->SetCut("DCUTE",cut1); /** delta-rays by electrons (GeV)*/ + //gMC->SetCut("DCUTM",cut1); /** delta-rays by muons (GeV)*/ + //gMC->SetCut("PPCUTM",cut1); /** direct pair production by muons (GeV)*/ + //gMC->SetCut("TOFMAX",tofmax); /**time of flight cut in seconds*/ } From f3ad15d8eed6bfa1e3d2124591f468d42919628e Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 3 Mar 2026 19:12:34 +0100 Subject: [PATCH 87/99] Geant4: set the particle production range cut through sndsw The correct spell was found in deprecated documentation https://root.cern/d/special-cuts-and-regions.html Also adding a few more useful commands for future debugging. --- gconfig/g4config.in | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gconfig/g4config.in b/gconfig/g4config.in index e61e8d4207..5f918a17eb 100644 --- a/gconfig/g4config.in +++ b/gconfig/g4config.in @@ -5,6 +5,13 @@ /mcVerbose/all 0 /mcTracking/loopVerbose 0 # suggested by Ivana Hrivnacova to switch off *** Particle reached max step number .... +# Geant4 range cut for all geo regions and all supported particle types: e+,e-, gamma, proton +/mcPhysics/rangeCuts 2 mm +# range cut for all geo regions and selected particle e.g. +#/mcPhysics/rangeCutForGamma 2 mm +# print the Geant4 and VMC cuts per material +#/mcRegions/print true + # switch on other sources of di muon /physics_lists/em/PositronToMuons true /physics_lists/em/GammaToMuons true From 39c7482fefff5f3bb7b4370fd4f9e986ffd00d38 Mon Sep 17 00:00:00 2001 From: siilieva Date: Wed, 4 Mar 2026 10:30:58 +0100 Subject: [PATCH 88/99] run_simSND: Set the kin E threshold for tracks' storage from the command line This is not a production cut, but a storage one. If a particle's Ekin drops below the cut value, its MC track is not saved to the MC track stack. However that particle is still tracked and energy loss is generated thus MC points are created and stored. These MC points have fTrackID=-2. The default value for the Ekin storage cut is the usual 100 MeV. --- shipLHC/run_simSND.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shipLHC/run_simSND.py b/shipLHC/run_simSND.py index 45ae972084..824c12322a 100644 --- a/shipLHC/run_simSND.py +++ b/shipLHC/run_simSND.py @@ -32,6 +32,7 @@ parser.add_argument("--pID", dest="pID", help="id of particle used by the gun (default=22)", required=False, default=22, type=int) parser.add_argument("--Estart", dest="Estart", help="start of energy range of particle gun for muflux detector (default=10 GeV)", required=False, default=10, type=float) parser.add_argument("--Eend", dest="Eend", help="end of energy range of particle gun for muflux detector (default=10 GeV)", required=False, default=10, type=float) +parser.add_argument('--eMin_store', type=float, help="kinetic energy cut for !particle storage! in MeV", dest='emin_store', default=100.) parser.add_argument("--PGrunID", dest="PGrunID",help="PG run ID", required=False, type=int) parser.add_argument("--multiplePGSources", help="Multiple particle guns in a x-y plane at a fixed z or in a 3D volume", action="store_true") @@ -325,10 +326,10 @@ def Exec(self,opt): fStack.SetEnergyCut(-100.*u.MeV) elif MCTracksWithEnergyCutOnly: fStack.SetMinPoints(-1) - fStack.SetEnergyCut(100.*u.MeV) + fStack.SetEnergyCut(options.emin_store*u.MeV) elif MCTracksWithHitsOrEnergyCut: fStack.SetMinPoints(1) - fStack.SetEnergyCut(100.*u.MeV) + fStack.SetEnergyCut(options.emin_store*u.MeV) elif options.deepCopy: fStack.SetMinPoints(0) fStack.SetEnergyCut(0.*u.MeV) From 40b3bb248a2a3c17755d0620c1f17b3c9fc5640f Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 13 Mar 2026 17:13:42 +0100 Subject: [PATCH 89/99] MuFi signal speed: refine points used for fit --- shipLHC/scripts/Survey-MufiScifi.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/shipLHC/scripts/Survey-MufiScifi.py b/shipLHC/scripts/Survey-MufiScifi.py index 3653adff90..75f12dfaeb 100644 --- a/shipLHC/scripts/Survey-MufiScifi.py +++ b/shipLHC/scripts/Survey-MufiScifi.py @@ -2832,7 +2832,7 @@ def analyze_EfficiencyAndResiduals(readHists=False,mode='S',local=True,zoom=Fals hist.GetYaxis().SetRangeUser(ymin,ymax) hist.Draw('colz') # get time x correlation, X = m*dt + b - h['gdtLRvsX_'+key] = ROOT.TGraph() + h['gdtLRvsX_'+key] = ROOT.TGraphErrors() g = h['gdtLRvsX_'+key] xproj = hist.ProjectionX('tmpx') if xproj.GetSumOfWeights()==0: continue @@ -2844,9 +2844,14 @@ def analyze_EfficiencyAndResiduals(readHists=False,mode='S',local=True,zoom=Fals X = hist.GetXaxis().GetBinCenter(nx) rc = tmp.Fit('gaus','NQS') res = rc.Get() - if not res: dt = tmp.GetMean() - else: dt = res.Parameter(1) + if not res: + dt = tmp.GetMean() + err = tmp.GetStd() + else: + dt = res.Parameter(1) + err =res.Parameter(2) g.SetPoint(np,X,dt) + g.SetPointError(np, 0, err) np+=1 g.SetLineColor(ROOT.kRed) g.SetLineWidth(2) From 4e174902abf63fa46ef061ea4043a4de2992ffb4 Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 17 Mar 2026 17:11:58 +0100 Subject: [PATCH 90/99] digi: Update MuFilter signal speed and attenuation length Using 2022-2025 datasets these parameters were revised. The atten. length is much dependent on the bar. Precise simulation of the response will have to feature per-bar fluctuations as these are large 130-330cm for US and Veto! For now using a single value. Perhaps it is enough given the bars are only 42cm(Veto) and 82cm(US) long. Note these parameters are also slightly dependent on the daq calibration. So a meticulous model will have to take this into account. --- geometry/sndLHC_TI18geom_config.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/geometry/sndLHC_TI18geom_config.py b/geometry/sndLHC_TI18geom_config.py index 1870adc651..27136b94b5 100644 --- a/geometry/sndLHC_TI18geom_config.py +++ b/geometry/sndLHC_TI18geom_config.py @@ -317,15 +317,16 @@ c.MuFilter.DS4ShiftX = 0.0 #digitization parameters - c.MuFilter.DsAttenuationLength = 350 * u.cm # values between 300 cm and 400cm observed for H6 testbeam - c.MuFilter.DsTAttenuationLength = 700 * u.cm # top readout with mirror on bottom - c.MuFilter.VandUpAttenuationLength = 999 * u.cm # no significante attenuation observed for H6 testbeam + c.MuFilter.DsAttenuationLength = 230*u.cm # values between 130cm and 330cm are observed for TI18 in years 2022-2025, but between 300 cm and 400cm for H6 testbeam + c.MuFilter.DsTAttenuationLength = 700*u.cm # top readout with mirror on bottom, TI18 and H6 observables agree + c.MuFilter.VandUpAttenuationLength = 210*u.cm # while no significant attenuation observed for H6 testbeam + c.MuFilter.VTAttenuationLength = 999*u.cm # Veto 3, no significant attenuation observed in 2024-2025 data c.MuFilter.VandUpSiPMcalibrationL = 50.*1000. # 1.65 MeV = 41 qcd over 6 Large SiPMs(one side) c.MuFilter.VandUpSiPMcalibrationS = 0. # no MIP signal for small SiPMs, delayed and compromised response in general c.MuFilter.DsSiPMcalibration = 25.*1000. c.MuFilter.timeResol = 150.*u.picosecond - c.MuFilter.VandUpPropSpeed = 12.5*u.cm/u.nanosecond - c.MuFilter.DsPropSpeed = 14.9*u.cm/u.nanosecond + c.MuFilter.VandUpPropSpeed = 13.6*u.cm/u.nanosecond + c.MuFilter.DsPropSpeed = 15.1*u.cm/u.nanosecond c.Floor = AttrDict(z=48000.*u.cm) # to place tunnel in SND_@LHC coordinate system c.Floor.DX = 1.0*u.cm From 68f7cfc33025e67f2a973c8689025d2b0d06815a Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 17 Mar 2026 17:58:06 +0100 Subject: [PATCH 91/99] TB digi: Update MuFilter signal speed and attenuation length --- geometry/sndLHC_H4geom_config.py | 17 +++++++++-------- geometry/sndLHC_H6geom_config.py | 15 ++++++++------- geometry/sndLHC_HXgeom_config.py | 15 ++++++++------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/geometry/sndLHC_H4geom_config.py b/geometry/sndLHC_H4geom_config.py index e7349b5a6a..ebc877d4ca 100644 --- a/geometry/sndLHC_H4geom_config.py +++ b/geometry/sndLHC_H4geom_config.py @@ -259,16 +259,17 @@ c.MuFilter.DS4ShiftX = 1.39 * u.cm #digitization parameters - c.MuFilter.DsAttenuationLength = 350 * u.cm # values between 300 cm and 400cm observed for H6 testbeam - c.MuFilter.DsTAttenuationLength = 700 * u.cm # top readout with mirror on bottom - c.MuFilter.VandUpAttenuationLength = 999 * u.cm # no significante attenuation observed for H6 testbeam - c.MuFilter.VandUpSiPMcalibrationL = 25.*1000. # 1.65 MeV = 41 qcd - c.MuFilter.VandUpSiPMcalibrationS = 25.*1000. + c.MuFilter.DsAttenuationLength = 230*u.cm # values between 130cm and 330cm are observed for TI18 in years 2022-2025, but between 300 cm and 400cm for H6 testbeam + c.MuFilter.DsTAttenuationLength = 700*u.cm # top readout with mirror on bottom, TI18 and H6 observables agree + c.MuFilter.VandUpAttenuationLength = 210*u.cm # while no significant attenuation observed for H6 testbeam + c.MuFilter.VTAttenuationLength = 999*u.cm # Veto 3, no significant attenuation observed in 2024-2025 data + c.MuFilter.VandUpSiPMcalibrationL = 50.*1000. # 1.65 MeV = 41 qcd over 6 Large SiPMs(one side) + c.MuFilter.VandUpSiPMcalibrationS = 0. # no MIP signal for small SiPMs, delayed and compromised response in general c.MuFilter.DsSiPMcalibration = 25.*1000. c.MuFilter.timeResol = 150.*u.picosecond - c.MuFilter.VandUpPropSpeed = 12.5*u.cm/u.nanosecond - c.MuFilter.DsPropSpeed = 14.3*u.cm/u.nanosecond - + c.MuFilter.VandUpPropSpeed = 13.6*u.cm/u.nanosecond + c.MuFilter.DsPropSpeed = 15.1*u.cm/u.nanosecond + c.Floor = AttrDict(z=48000.*u.cm) # to place tunnel in SND_@LHC coordinate system c.Floor.DX = 1.0*u.cm c.Floor.DY = -4.5*u.cm # subtract 4.5cm to avoid overlaps diff --git a/geometry/sndLHC_H6geom_config.py b/geometry/sndLHC_H6geom_config.py index 0951ef5a75..17560ef099 100644 --- a/geometry/sndLHC_H6geom_config.py +++ b/geometry/sndLHC_H6geom_config.py @@ -245,15 +245,16 @@ c.MuFilter.VETOBoxY2 = c.MuFilter.VETOLocY + c.MuFilter.VetoBarZ/2 + c.MuFilter.SupportBoxD #digitization parameters - c.MuFilter.DsAttenuationLength = 350 * u.cm # values between 300 cm and 400cm observed for H6 testbeam - c.MuFilter.DsTAttenuationLength = 700 * u.cm # top readout with mirror on bottom - c.MuFilter.VandUpAttenuationLength = 999 * u.cm # no significante attenuation observed for H6 testbeam - c.MuFilter.VandUpSiPMcalibrationL = 25.*1000. # 1.65 MeV = 41 qcd - c.MuFilter.VandUpSiPMcalibrationS = 25.*1000. + c.MuFilter.DsAttenuationLength = 230*u.cm # values between 130cm and 330cm are observed for TI18 in years 2022-2025, but between 300 cm and 400cm for H6 testbeam + c.MuFilter.DsTAttenuationLength = 700*u.cm # top readout with mirror on bottom, TI18 and H6 observables agree + c.MuFilter.VandUpAttenuationLength = 210*u.cm # while no significant attenuation observed for H6 testbeam + c.MuFilter.VTAttenuationLength = 999*u.cm # Veto 3, no significant attenuation observed in 2024-2025 data + c.MuFilter.VandUpSiPMcalibrationL = 50.*1000. # 1.65 MeV = 41 qcd over 6 Large SiPMs(one side) + c.MuFilter.VandUpSiPMcalibrationS = 0. # no MIP signal for small SiPMs, delayed and compromised response in general c.MuFilter.DsSiPMcalibration = 25.*1000. c.MuFilter.timeResol = 150.*u.picosecond - c.MuFilter.VandUpPropSpeed = 12.5*u.cm/u.nanosecond - c.MuFilter.DsPropSpeed = 14.3*u.cm/u.nanosecond + c.MuFilter.VandUpPropSpeed = 13.6*u.cm/u.nanosecond + c.MuFilter.DsPropSpeed = 15.1*u.cm/u.nanosecond c.Floor = AttrDict(z=48000.*u.cm) # to place tunnel in SND_@LHC coordinate system c.Floor.DX = 1.0*u.cm diff --git a/geometry/sndLHC_HXgeom_config.py b/geometry/sndLHC_HXgeom_config.py index be48a76012..3b57be8b3d 100644 --- a/geometry/sndLHC_HXgeom_config.py +++ b/geometry/sndLHC_HXgeom_config.py @@ -256,15 +256,16 @@ c.MuFilter.DS4ShiftX = 1.39 * u.cm #digitization parameters - c.MuFilter.DsAttenuationLength = 350 * u.cm # values between 300 cm and 400cm observed for H6 testbeam - c.MuFilter.DsTAttenuationLength = 700 * u.cm # top readout with mirror on bottom - c.MuFilter.VandUpAttenuationLength = 999 * u.cm # no significante attenuation observed for H6 testbeam - c.MuFilter.VandUpSiPMcalibrationL = 25.*1000. # 1.65 MeV = 41 qcd - c.MuFilter.VandUpSiPMcalibrationS = 25.*1000. + c.MuFilter.DsAttenuationLength = 230*u.cm # values between 130cm and 330cm are observed for TI18 in years 2022-2025, but between 300 cm and 400cm for H6 testbeam + c.MuFilter.DsTAttenuationLength = 700*u.cm # top readout with mirror on bottom, TI18 and H6 observables agree + c.MuFilter.VandUpAttenuationLength = 210*u.cm # while no significant attenuation observed for H6 testbeam + c.MuFilter.VTAttenuationLength = 999*u.cm # Veto 3, no significant attenuation observed in 2024-2025 data + c.MuFilter.VandUpSiPMcalibrationL = 50.*1000. # 1.65 MeV = 41 qcd over 6 Large SiPMs(one side) + c.MuFilter.VandUpSiPMcalibrationS = 0. # no MIP signal for small SiPMs, delayed and compromised response in general c.MuFilter.DsSiPMcalibration = 25.*1000. c.MuFilter.timeResol = 150.*u.picosecond - c.MuFilter.VandUpPropSpeed = 12.5*u.cm/u.nanosecond - c.MuFilter.DsPropSpeed = 14.3*u.cm/u.nanosecond + c.MuFilter.VandUpPropSpeed = 13.6*u.cm/u.nanosecond + c.MuFilter.DsPropSpeed = 15.1*u.cm/u.nanosecond c.Floor = AttrDict(z=48000.*u.cm) # to place tunnel in SND_@LHC coordinate system c.Floor.DX = 1.0*u.cm From 145307cedc607ff5e930ff864cb8cde854b3662b Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 17 Mar 2026 18:14:14 +0100 Subject: [PATCH 92/99] MuFi digi: use Veto 3 attenuation length --- shipLHC/MuFilterHit.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shipLHC/MuFilterHit.cxx b/shipLHC/MuFilterHit.cxx index 42d08bf46f..ed2c0a8fe6 100644 --- a/shipLHC/MuFilterHit.cxx +++ b/shipLHC/MuFilterHit.cxx @@ -60,7 +60,7 @@ MuFilterHit::MuFilterHit(Int_t detID, std::vector V) else { if (floor(detID/10000)==1 && nSides==1){ // top readout with mirror on bottom - attLength = 2*MuFilterDet->GetConfParF("MuFilter/VandUpAttenuationLength"); + attLength = 2*MuFilterDet->GetConfParF("MuFilter/VTAttenuationLength"); } else {attLength = MuFilterDet->GetConfParF("MuFilter/VandUpAttenuationLength");} siPMcalibration = MuFilterDet->GetConfParF("MuFilter/VandUpSiPMcalibrationL"); From 768bd291178d6a16f3e9e5e9fda289311a96db55 Mon Sep 17 00:00:00 2001 From: siilieva Date: Tue, 17 Mar 2026 18:16:44 +0100 Subject: [PATCH 93/99] run_digi: Read digi params from geofile, if these exist Urge the user to regenerate the MC production, not only re-digitize, shall they use outdated sim files. This state is easily detectable through the digi parameters as the update of the latter coincides with the *major* update of Geant4 production cut and physics list! --- shipLHC/run_digiSND.py | 46 +++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/shipLHC/run_digiSND.py b/shipLHC/run_digiSND.py index 11dedfe681..04a9e26e64 100644 --- a/shipLHC/run_digiSND.py +++ b/shipLHC/run_digiSND.py @@ -71,20 +71,48 @@ def mem_monitor(): lsOfGlobals = ROOT.gROOT.GetListOfGlobals() scifiDet = lsOfGlobals.FindObject('Scifi') mufiDet = lsOfGlobals.FindObject('MuFilter') -mufiDet.SetConfPar("MuFilter/DsAttenuationLength",350 * u.cm) # values between 300 cm and 400cm observed for H6 testbeam -mufiDet.SetConfPar("MuFilter/DsTAttenuationLength",700 * u.cm) # top readout with mirror on bottom -mufiDet.SetConfPar("MuFilter/VandUpAttenuationLength",999 * u.cm) # no significante attenuation observed for H6 testbeam -mufiDet.SetConfPar("MuFilter/DsSiPMcalibrationS",25.*1000.) # in MC: 1.65 keV are about 41.2 qdc -mufiDet.SetConfPar("MuFilter/VandUpPropSpeed",12.5*u.cm/u.nanosecond); -mufiDet.SetConfPar("MuFilter/DsPropSpeed",14.3*u.cm/u.nanosecond); scifiDet.SetConfPar("Scifi/nphe_min",options.ts) # threshold scifiDet.SetConfPar("Scifi/nphe_max",options.ss) # saturation -scifiDet.SetConfPar("Scifi/timeResol",150.*u.picosecond) # time resolution in ps -scifiDet.SetConfPar("MuFilter/timeResol",150.*u.picosecond) # time resolution in ps, first guess + +#### +# The lines below aim to reproduce the original digitization case, but urge the user to regenerate +# the sample shall it be outdated from before the removal of the 1MeV production cut, which +# coincides with MuFi digi const update. +# # in MC productions generated before July 2022 Scifi signal speed is missing from the geofile +better_update = False if scifiDet.GetConfParF("Scifi/signalSpeed")==0: scifiDet.SetConfPar("Scifi/signalSpeed", 15*u.cm/u.nanosecond) - + better_update = True +# geofiles before March 2026 don't have the Veto 3 atten.length +if mufiDet.GetConfParF("MuFilter/VTAttenuationLength")==0: + mufiDet.SetConfPar("MuFilter/VTAttenuationLength",999*u.cm) + better_update = True +# old digi constants from before the MuFi response update +if mufiDet.GetConfParF("MuFilter/VandUpAttenuationLength")==999*u.cm: + better_update = True +# in very ancient MC productions it is possible some digitization params are missing +# set them here values updated in March 2026. +if mufiDet.GetConfParF("MuFilter/DsAttenuationLength")==0 or\ + mufiDet.GetConfParF("MuFilter/VandUpPropSpeed")==0 : + mufiDet.SetConfPar("MuFilter/DsAttenuationLength",230*u.cm) + mufiDet.SetConfPar("MuFilter/DsTAttenuationLength",700*u.cm) + mufiDet.SetConfPar("MuFilter/VandUpAttenuationLength",210*u.cm) + mufiDet.SetConfPar("MuFilter/VTAttenuationLength",999*u.cm) + mufiDet.SetConfPar("MuFilter/DsSiPMcalibration",25.*1000.) + # 1.65 MeV = 41 qcd over 6 Large SiPMs(one side) + mufiDet.SetConfPar("MuFilter/VandUpSiPMcalibrationL",50.*1000.) + # no MIP signal for small SiPMs, delayed and compromised response in general + mufiDet.SetConfPar("MuFilter/VandUpSiPMcalibrationS",0.) + mufiDet.SetConfPar("MuFilter/VandUpPropSpeed",13.6*u.cm/u.nanosecond); + mufiDet.SetConfPar("MuFilter/DsPropSpeed",15.1*u.cm/u.nanosecond); + scifiDet.SetConfPar("Scifi/timeResol",150.*u.picosecond) + mufiDet.SetConfPar("MuFilter/timeResol",150.*u.picosecond) # time resolution in ps, first guess + better_update = True + +if better_update: + print("WARNING: Simulation file preceding the production cut change! Consider regenerating from scratch!") +#### # Fair digitization task if options.FairTask_digi: From cce8037bc61c9292dc126c8437c703627a20a427 Mon Sep 17 00:00:00 2001 From: siilieva <84918585+siilieva@users.noreply.github.com> Date: Fri, 20 Mar 2026 14:21:01 +0100 Subject: [PATCH 94/99] update CHANGELOG before next release v1.4.0 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4865f8c449..e643e93fae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,33 @@ We start with the first sndsw release: v1.0.0+2025-07-updateScifi. Shall there be a strong will/need, one can go back and create and fill in the logs for previous stacks. +## v1.4.0+2026-01-G4cutoff + +### Added + +- Geant4 range cut set to 2mm +- Record simulation run and event ID +- `is_mc` option for sndConfiguration, used in all sndPlane classes +- Veto and DS plane classes +- 2025 data file locations for the analysis tools + +### Changed + +- Geant4 physics list to FTFP_BERT_HP_EMZ +- retire small SiPMs: add a flag to skip(default) them in analysis + +### Removed + +- Geant4 production cut at 1MeV +- hard coded digi params in run_digi macro + +### Fixed + +- MeV to qdc conversion for US +- Scifi station 5 rotation for target 256 +- revised the drift velocity and attenuation length of Veto/US/DS using 2022-2025 data + + ## v1.3.0+2025-11-showerToolsAndP8Decayer ### Added From 7bdb313bac8b355e67fde1236f3ae2b9f1eeca80 Mon Sep 17 00:00:00 2001 From: siilieva Date: Wed, 8 Apr 2026 09:31:37 +0200 Subject: [PATCH 95/99] Add detector alignment for targets 261 and 262 MuFilter spatial alignment consts are very similar to the 2025 ones, as should be given the detectors were not moved. Anyways, since we moved to a geofile per year, better recalibrate and use new values. --- shipLHC/modifyGeoFileDict.py | 62 ++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/shipLHC/modifyGeoFileDict.py b/shipLHC/modifyGeoFileDict.py index 86c14c669d..bf4586b6b9 100644 --- a/shipLHC/modifyGeoFileDict.py +++ b/shipLHC/modifyGeoFileDict.py @@ -10,7 +10,7 @@ commonPath = "/eos/experiment/sndlhc/convertedData/physics/" supportedGeoFiles = {} -supported_years = [2022, 2023, 2024, 2025] +supported_years = [2022, 2023, 2024, 2025, 2026] for year in supported_years: supportedGeoFiles["geofile_sndlhc_TI18_V0_"+str(year)+".root"] = commonPath+str(year)+"/" @@ -38,7 +38,8 @@ def modifyDicts(year=2024): 2022: [ 0.11,-0.04, 0.00, 0.10, 0.26, 0.24, 0.31, 0.34, 0.43, 1.13, 0.53, 1.31, 0.61, 1.35, 1.39], 2023: [ 0.15, 0.03, 0.00,-0.01, 0.09,-0.01, 0.06, 0.06, 0.49, 0.86, 0.20, 0.98, 0.24, 1.01, 1.11], 2024: [-0.06, 0.04, 0.65,-0.15,-0.12,-0.26,-0.29,-0.36, 0.00, 0.35,-0.37, 0.38,-0.41, 0.30, 0.35], - 2025: [-0.04, 0.05, 0.66,-0.16,-0.15,-0.31,-0.34,-0.45,-0.10, 0.39,-0.48, 0.43,-0.91,-0.36,-2.02] + 2025: [-0.04, 0.05, 0.66,-0.16,-0.15,-0.31,-0.34,-0.45,-0.10, 0.39,-0.48, 0.43,-0.91,-0.36,-2.02], + 2026: [-0.06, 0.06, 0.62,-0.17,-0.17,-0.30,-0.34,-0.43,-0.08, 0.44,-0.46, 0.48,-0.89,-0.34,-2.01] } mufi_spatial_aligment_keys = ['Veto1ShiftY','Veto2ShiftY','Veto3ShiftX', 'US1ShiftY','US2ShiftY','US3ShiftY','US4ShiftY','US5ShiftY', @@ -79,14 +80,18 @@ def modifyDicts(year=2024): constants['t_11795']= [-6.43,-6.45,-6.71,-6.79,-6.43,-6.52,-5.99,-6.09,-6.15,-6.32,-5.98,-6.13,-8.17,-8.25,-8.29,-8.48,-8.08,-8.14,-8.16,-8.24] constants['t_12021']= [-6.46,-6.53,-6.79,-6.89,-6.49,-6.62,-6.02,-6.13,-6.24,-6.40,-6.05,-6.22,-8.21,-8.28,-8.38,-8.57,-8.15,-8.25,-8.22,-8.32] constants['t_12123']= [-6.60,-6.65,-6.74,-6.84,-6.46,-6.57,-6.06,-6.13,-6.15,-6.33,-6.00,-6.16,-8.20,-8.26,-8.30,-8.49,-8.13,-8.23,-8.27,-8.40] + #2026 + constants['t_12794']= [-6.52,-6.59,-6.70,-6.77,-6.42,-6.59,-5.89,-5.98,-6.19,-6.34,-5.97,-6.18,-8.15,-8.19,-8.25,-8.45,-8.08,-8.19,-8.08,-8.26] + constants['t_13118']= [-6.50,-6.56,-6.66,-6.74,-6.39,-6.51,-5.88,-5.99,-6.16,-6.31,-5.95,-6.13,-8.13,-8.16,-8.20,-8.39,-8.04,-8.20,-8.05,-8.23] ds_time_aligment_consts = { 2022: {"t_0":0.000, "t_4361":0.082, "t_5117":0.085}, 2023: {"t_0":0.000, "t_5478":0.082, "t_6208":0.086, "t_6443":0.082, "t_6677":0.084}, - 2024: {"t_0":0.000, "t_7649":0.081, "t_8318":0.082, "t_8583":0.081, "t_8942":0.080, + 2024: {"t_0":0.000, "t_7649":0.081, "t_8318":0.082, "t_8583":0.081, "t_8942":0.080, "t_9156":0.083, "t_9286":0.082, "t_9379":0.083, "t_9462":0.083, "t_9613":0.082, "t_9692":0.078, "t_9882":0.084, "t_10012":0.085}, 2025: {"t_0": 0.000, "t_10423":0.083, "t_11158":0.082, "t_11576":0.079, "t_11676":0.080, - "t_11795": 0.079, "t_12021":0.077, "t_12123":0.081} + "t_11795": 0.079, "t_12021":0.077, "t_12123":0.081}, + 2026: {"t_0": 0.000, "t_12794":0.082, "t_13118":0.081} } slopes_dict = ds_time_aligment_consts[year] #time delay corrections first order, only for DS at the moment @@ -231,13 +236,25 @@ def modifyDicts(year=2024): -1.092*u.ns, 0.000*u.ns, -0.038*u.ns, 0.568*u.ns, 0.909*u.ns, -0.522*u.ns, 0.968*u.ns, -0.445*u.ns, 0.000*u.ns, -1.315*u.ns, 0.328*u.ns, -2.095*u.ns, -1.545*u.ns, -0.743*u.ns, -1.662*u.ns, 0.000*u.ns, -0.410*u.ns, -0.160*u.ns, 1.179*u.ns, -0.530*u.ns, 1.911*u.ns] +#2026 + constants['t_12794']=[ 0.000*u.ns, 0.000*u.ns, -0.325*u.ns, -0.529*u.ns, 0.148*u.ns, -0.449*u.ns, -0.082*u.ns, + -1.478*u.ns, 0.000*u.ns, 0.755*u.ns, -0.195*u.ns, 0.165*u.ns, 0.760*u.ns, 0.255*u.ns, + -1.026*u.ns, 0.000*u.ns, -0.713*u.ns, 0.431*u.ns, 0.806*u.ns, -0.560*u.ns, 0.880*u.ns, + -0.461*u.ns, 0.000*u.ns, -1.388*u.ns, 0.304*u.ns, -2.082*u.ns, -1.489*u.ns, -0.679*u.ns, + -1.676*u.ns, 0.000*u.ns, -0.464*u.ns, -0.170*u.ns, 1.052*u.ns, -0.551*u.ns, 1.918*u.ns] + constants['t_13118']=[ 0.000*u.ns, 0.000*u.ns, -0.330*u.ns, -0.506*u.ns, 0.170*u.ns, -0.408*u.ns, -0.052*u.ns, + -1.409*u.ns, 0.000*u.ns, 0.714*u.ns, -0.277*u.ns, 0.116*u.ns, 0.725*u.ns, 0.222*u.ns, + -0.997*u.ns, 0.000*u.ns, -1.387*u.ns, 0.493*u.ns, 0.817*u.ns, -0.552*u.ns, 0.854*u.ns, + -0.431*u.ns, 0.000*u.ns, -1.428*u.ns, 0.305*u.ns, -2.108*u.ns, -1.494*u.ns, -0.690*u.ns, + -1.652*u.ns, 0.000*u.ns, -0.456*u.ns, -0.151*u.ns, 1.117*u.ns, -0.559*u.ns, 1.926*u.ns] # scifi_time_aligment_consts = { 2022: ['t_0', 't_4361','t_5117'], 2023: ['t_0', 't_5478', 't_6208', 't_6443', 't_6677'], 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'], - 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795', 't_12021', 't_12123'] + 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795', 't_12021', 't_12123'], + 2026: ['t_0', 't_12794', 't_13118'] } scifi_time_tags = scifi_time_aligment_consts[year] for c in scifi_time_tags: @@ -685,13 +702,46 @@ def modifyDicts(year=2024): 0.45*u.mrad, 0.05*u.mrad, 0.00*u.mrad, 0.39*u.mrad, -0.09*u.mrad, 0.01*u.mrad, -0.59*u.mrad, 0.01*u.mrad, 0.00*u.mrad] + alignment['t_12794']=[ #2026 emulsion run 26 run_261 + 148.00*u.um, 180.00*u.um, 176.00*u.um, + -160.00*u.um, -12.00*u.um, -72.00*u.um, + -278.00*u.um, -198.00*u.um, -285.00*u.um, + 75.00*u.um, 225.00*u.um, 117.00*u.um, + 20.00*u.um, 60.00*u.um, -34.00*u.um, + 105.00*u.um, 90.00*u.um, 190.00*u.um, + 188.00*u.um, 240.00*u.um, 290.00*u.um, + -200.00*u.um, -322.00*u.um, -236.00*u.um, + -110.00*u.um, -130.00*u.um, -160.00*u.um, + 210.00*u.um, 400.00*u.um, 345.00*u.um, + -0.38*u.mrad, -0.10*u.mrad, 0.00*u.mrad, + 0.30*u.mrad, 0.10*u.mrad, 0.00*u.mrad, + 0.35*u.mrad, 0.30*u.mrad, 0.00*u.mrad, + -1.00*u.mrad, -0.40*u.mrad, 0.00*u.mrad, + 0.90*u.mrad, 0.30*u.mrad, 0.00*u.mrad] + alignment['t_13118']=[ #2026 emulsion run 27 run_262 + 110.00*u.um, 170.00*u.um, 138.00*u.um, + -88.00*u.um, 64.00*u.um, 0.00*u.um, + -154.00*u.um, -81.00*u.um, -140.00*u.um, + -7.00*u.um, 126.20*u.um, 41.00*u.um, + -5.00*u.um, 55.00*u.um, -47.00*u.um, + 7.00*u.um, -31.00*u.um, 80.20*u.um, + -109.80*u.um, -33.00*u.um, -10.00*u.um, + 152.00*u.um, 58.00*u.um, 96.00*u.um, + 165.80*u.um, 153.00*u.um, 95.00*u.um, + -92.00*u.um, 126.00*u.um, 71.00*u.um, + -0.42*u.mrad, -0.08*u.mrad, 0.20*u.mrad, + -0.08*u.mrad, 0.00*u.mrad, 0.00*u.mrad, + 0.15*u.mrad, 0.00*u.mrad, 0.00*u.mrad, + 0.20*u.mrad, 0.00*u.mrad, 0.00*u.mrad, + -0.20*u.mrad, 0.00*u.mrad, 0.00*u.mrad] scifi_spatial_aligment_consts = { 2022: ['t_0', 't_4361','t_4575','t_4855','t_5172'], 2023: ['t_0', 't_5431', 't_6443', 't_6677'], 2024: ['t_0', 't_7649', 't_8318', 't_8583', 't_8942', 't_9156', 't_9286', 't_9379', 't_9462', 't_9613', 't_9692', 't_9882', 't_10012'], - 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795', 't_12021', 't_12123'] + 2025: ['t_0', 't_10423', 't_11158', 't_11576', 't_11676', 't_11795', 't_12021', 't_12123'], + 2026: ['t_0', 't_12794', 't_13118'] } scifi_spatial_tags = scifi_spatial_aligment_consts[year] for c in scifi_spatial_tags: From 2abc4189d1a71571c71c9daae4621e71475b5552 Mon Sep 17 00:00:00 2001 From: siilieva Date: Wed, 19 Nov 2025 08:50:48 +0100 Subject: [PATCH 96/99] FS extraction support for 2025 --- shipLHC/scripts/FillingScheme.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shipLHC/scripts/FillingScheme.py b/shipLHC/scripts/FillingScheme.py index 8c7e30037b..f54154ad96 100644 --- a/shipLHC/scripts/FillingScheme.py +++ b/shipLHC/scripts/FillingScheme.py @@ -117,6 +117,7 @@ def getNameOfFillingscheme(self,fillnr): Y = "2022" if fillnr > 8500 : Y = "2023" if fillnr >= 9323 : Y="2024" + if fillnr >= 10406 : Y="2025" if not self.lpcFillingscheme: with urlopen('https://lpc.web.cern.ch/cgi-bin/fillTable.py?year='+Y) as webpage: self.lpcFillingscheme = webpage.read().decode() @@ -177,6 +178,7 @@ def getLumiAtIP1(self,fillnr=None,fromnxcals=False, fromAtlas=False): Y = "2022" if fillnr>8500: Y = "2023" if fillnr>=9323: Y="2024" + if fillnr>=10406 : Y="2025" if not fromnxcals and not fromAtlas: try: with urlopen('https://lpc.web.cern.ch/cgi-bin/fillAnalysis.py?year='+Y+'&action=fillData&exp=ATLAS&fillnr='+str(fillnr)) as webpage: @@ -440,12 +442,12 @@ def extractFillingScheme(self,fillNr): print('Name of filling scheme: ',fs_name_table) if fs_name_table==0: return -1 - # since 2024 new there is new storage of FS in json files, no csv + # since 2024 there is new storage of FS in json files, no csv fs_url="https://gitlab.cern.ch/lhc-injection-scheme/injection-schemes/-/raw/master/"+\ fs_name_table+".json" F = ROOT.TFile(self.path+'fillingScheme-'+fillNr+'.root','recreate') nt = ROOT.TNtuple('fill'+fillNr,'b1 IP1 IP2','B1:IP1:IP2:IsB2') - # Get the 2024 data + # Get the data for year>=2024 # 9323 is first fillNr for 2024 if int(fillNr) >= 9323: # Get the JSON file content from the web @@ -2063,6 +2065,12 @@ def modifyFSdict(self,shift=-1): options.convpath = "/eos/experiment/sndlhc/convertedData/physics/2024/"+em_run options.rmin = 7649-1 offline =www+"offline.html" + elif options.rawData.find('2025')>0: + # extract the em target run from the rawData path + em_run = options.rawData[options.rawData.find("run_"):] + options.convpath = "/eos/experiment/sndlhc/convertedData/physics/2025/"+em_run + options.rmin = 10919-1 #10587 fillN + offline =www+"offline.html" FS = fillingScheme() FS.Init(options) ut.bookCanvas(FS.h,'c1','c1',1800,900,1,1) From d55592d0361d6738d8a2a61f499131cf87e135fe Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 26 Dec 2025 16:08:51 +0100 Subject: [PATCH 97/99] Conversion: identify and skip mini DT Another task will handle mini DT data. --- shipLHC/rawData/ConvRawData.py | 4 ++++ sndFairTasks/ConvRawData.cxx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/shipLHC/rawData/ConvRawData.py b/shipLHC/rawData/ConvRawData.py index 83aa283ba4..fb80ff5be1 100644 --- a/shipLHC/rawData/ConvRawData.py +++ b/shipLHC/rawData/ConvRawData.py @@ -504,6 +504,8 @@ def executeEvent1(self,eventNumber): system = self.MufiSystem[board_id][tofpet_id] key = (tofpet_id%2)*1000 + tofpet_channel tmp = self.boardMaps['MuFilter'][board][self.slots[tofpet_id]] + # mini DTs are labelled DS 5V, board is 32 + if (tmp == "DS_5Vert"): continue if self.options.debug or not tmp.find('not')<0: print('debug',tmp,system,key,board,tofpet_id,tofpet_id%2,tofpet_channel) sipmChannel = 99 if not key in self.TofpetMap[system]: @@ -639,6 +641,8 @@ def executeEvent0(self,eventNumber): system = self.MufiSystem[board_id][tofpet_id] key = (tofpet_id%2)*1000 + tofpet_channel tmp = self.boardMaps['MuFilter'][board][self.slots[tofpet_id]] + # mini DTs are labelled DS 5V, board is 32 + if (tmp == "DS_5Vert"): continue if self.options.debug or not tmp.find('not')<0: print('debug',tmp,system,key,board,tofpet_id,tofpet_id%2,tofpet_channel) sipmChannel = 99 if not key in self.TofpetMap[system]: diff --git a/sndFairTasks/ConvRawData.cxx b/sndFairTasks/ConvRawData.cxx index 32d897f2fd..c2b2fffeae 100644 --- a/sndFairTasks/ConvRawData.cxx +++ b/sndFairTasks/ConvRawData.cxx @@ -333,6 +333,8 @@ void ConvRawData::Process0() system = MufiSystem[board_id][tofpet_id]; key = (tofpet_id%2)*1000 + tofpet_channel; tmp = boardMapsMu["MuFilter"][board.first][slots[tofpet_id]]; + // mini DTs are labelled DS 5V, board is 32 + if (tmp == "DS_5Vert") continue; if (debug || !(tmp.find("not") == string::npos)) { LOG (info) << system << " " << key << " " << board.first << " " << tofpet_id @@ -627,6 +629,8 @@ void ConvRawData::Process1() system = MufiSystem[board_id][tofpet_id]; key = (tofpet_id%2)*1000 + tofpet_channel; tmp = boardMapsMu["MuFilter"][board_name][slots[tofpet_id]]; + // mini DTs are labelled DS 5V, board is 32 + if (tmp == "DS_5Vert") continue; if (debug || !(tmp.find("not") == string::npos)) { LOG (info) << system << " " << key << " " << board_name << " " << tofpet_id From 5a5d0a85338a0156856fddf3a537a4bf65eda366 Mon Sep 17 00:00:00 2001 From: siilieva Date: Fri, 26 Dec 2025 16:50:13 +0100 Subject: [PATCH 98/99] Conversion: Write the 2025 bunch structure to the coverted data file --- shipLHC/rawData/ConvRawData.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shipLHC/rawData/ConvRawData.py b/shipLHC/rawData/ConvRawData.py index fb80ff5be1..6c8f8ddf17 100644 --- a/shipLHC/rawData/ConvRawData.py +++ b/shipLHC/rawData/ConvRawData.py @@ -46,6 +46,7 @@ def Init(self,options): if options.path.find('2022')!=-1: fpath = "/eos/experiment/sndlhc/convertedData/physics/2022/" elif options.path.find('2023')!=-1: fpath = "/eos/experiment/sndlhc/convertedData/physics/2023/" elif options.path.find('2024')!=-1: fpath = "/eos/experiment/sndlhc/convertedData/physics/2024/" + elif options.path.find('2025')!=-1: fpath = "/eos/experiment/sndlhc/convertedData/physics/2025/" else: fpath = "/eos/experiment/sndlhc/convertedData/commissioning/TI18/" fg = ROOT.TFile.Open(options.server+fpath+"/FSdict.root") pkl = Unpickler(fg) From 21ed0dfee2c7137969234004ac3c1bd2944b5657 Mon Sep 17 00:00:00 2001 From: jutesare Date: Wed, 1 Apr 2026 15:16:40 +0200 Subject: [PATCH 99/99] Add feature from issue #216: added dark mode for 2dEventDisplay, including white SND@LHC logo. --- shipLHC/Large__SND_Logo_white_cut.png | Bin 0 -> 88153 bytes shipLHC/scripts/2dEventDisplay.py | 127 ++++++++++++++++---------- 2 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 shipLHC/Large__SND_Logo_white_cut.png diff --git a/shipLHC/Large__SND_Logo_white_cut.png b/shipLHC/Large__SND_Logo_white_cut.png new file mode 100644 index 0000000000000000000000000000000000000000..a4f8b1b614eff45a90e7c84d3d19dfd0d7cef90e GIT binary patch literal 88153 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 max_QDC : this_qdc = max_QDC - fillNode(curPath, ROOT.TColor.GetPalette()[int(this_qdc/max_QDC*(len(ROOT.TColor.GetPalette())-1))]) + if not darkMode: + fillNode(curPath, ROOT.TColor.GetPalette()[int(this_qdc/max_QDC*(len(ROOT.TColor.GetPalette())-1))]) + else: # for dark mode, only use the lighter part of the colormap; the dark hues have bad contrast + fillNode(curPath, ROOT.TColor.GetPalette()[int((1/4 + 3/4*this_qdc/max_QDC) * (len(ROOT.TColor.GetPalette())-1))]) else : - fillNode(curPath) + fillNode(curPath, darkMode=darkMode) if digi.isVertical(): F = 'firedChannelsX' else: F = 'firedChannelsY' @@ -591,17 +613,20 @@ def loopEvents( h[F][systems[system]][0]+=1 if len(h[F][systems[system]]) < 2+side: continue h[F][systems[system]][2+side]+=qdc - h['hitCollectionY']['Scifi'][1].SetMarkerColor(ROOT.kBlue+2) - h['hitCollectionX']['Scifi'][1].SetMarkerColor(ROOT.kBlue+2) + h['hitCollectionY']['Scifi'][1].SetMarkerColor(ROOT.kBlue+2 if not darkMode else ROOT.kBlue-4) + h['hitCollectionX']['Scifi'][1].SetMarkerColor(ROOT.kBlue+2 if not darkMode else ROOT.kBlue-4) if hitColour == "q" : for orientation in ['X', 'Y']: max_density = 40 density = np.clip(0, max_density, getSciFiHitDensity(h['hitCollection'+orientation]['Scifi'][1])) for i in range(h['hitCollection'+orientation]['Scifi'][1].GetN()) : - h['hitColour'+orientation]['Scifi'].append(ROOT.TColor.GetPalette()[int(float(density[i])/max_density*(len(ROOT.TColor.GetPalette())-1))]) + if not darkMode: + h['hitColour'+orientation]['Scifi'].append(ROOT.TColor.GetPalette()[int(float(density[i])/max_density*(len(ROOT.TColor.GetPalette())-1))]) + else: # for dark mode, only use the lighter part of the colormap; the dark hues have bad contrast + h['hitColour'+orientation]['Scifi'].append(ROOT.TColor.GetPalette()[int((1/4 + 3/4*float(density[i])/max_density) * (len(ROOT.TColor.GetPalette())-1))]) - drawLegend(max_density, max_QDC, 5) + drawLegend(max_density, max_QDC, 5, darkMode=darkMode) k = 1 moreEventInfo = [] @@ -609,7 +634,7 @@ def loopEvents( for collection in ['hitCollectionX','hitCollectionY']: h['simpleDisplay'].cd(k) - drawInfo(h['simpleDisplay'], k, runId, N, T) + drawInfo(h['simpleDisplay'], k, runId, N, T, darkMode=darkMode) k+=1 for c in h[collection]: F = collection.replace('hitCollection','firedChannels') @@ -645,12 +670,12 @@ def loopEvents( k = 1 for collection in ['hitCollectionX','hitCollectionY']: h['simpleDisplay'].cd(k) - drawInfo(h['simpleDisplay'], k, runId, N, T,moreEventInfo) + drawInfo(h['simpleDisplay'], k, runId, N, T,moreEventInfo, darkMode=darkMode) k+=1 h['simpleDisplay'].Update() if withTiming: timingOfEvent() - addTrack(OT) + addTrack(OT, darkMode=darkMode) # try finding shower direction and intercept if options.drawShowerDir: @@ -678,12 +703,12 @@ def loopEvents( h['simpleDisplay'].Update() if option == "2tracks": - rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=0.5) - if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=0.75) - if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=1.0) - if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=1.75) - if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=2.5) - if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=3.0) + rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=0.5, darkMode=darkMode) + if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=0.75, darkMode=darkMode) + if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=1.0, darkMode=darkMode) + if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=1.75, darkMode=darkMode) + if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=2.5, darkMode=darkMode) + if not rc: rc = twoTrackEvent(sMin=10,dClMin=7,minDistance=0.5,sepDistance=3.0, darkMode=darkMode) if verbose>0: dumpChannels() userProcessing(event) @@ -703,18 +728,18 @@ def loopEvents( if save: os.system("convert -delay 60 -loop 0 event*." + options.extension + " animated.gif") -def addTrack(OT,scifi=False): +def addTrack(OT,scifi=False, darkMode=False): xax = h['xz'].GetXaxis() nTrack = 0 for aTrack in OT.Reco_MuonTracks: trackColor = ROOT.kRed if aTrack.GetUniqueID()==1: - trackColor = ROOT.kBlue+2 + trackColor = ROOT.kBlue+2 if not darkMode else ROOT.kBlue-4 flightDir = trackTask.trackDir(aTrack) print('flight direction: %5.3F significance: %5.3F'%(flightDir[0],flightDir[1])) - if aTrack.GetUniqueID()==3: trackColor = ROOT.kBlack - if aTrack.GetUniqueID()==11: trackColor = ROOT.kAzure-2 # HT scifi track - if aTrack.GetUniqueID()==13: trackColor = ROOT.kGray+2 # HT ds track + if aTrack.GetUniqueID()==3: trackColor = ROOT.kBlack if not darkMode else ROOT.kWhite + if aTrack.GetUniqueID()==11: trackColor = ROOT.kAzure-2 if not darkMode else ROOT.kAzure-3 # HT scifi track + if aTrack.GetUniqueID()==13: trackColor = ROOT.kGray+2 if not darkMode else ROOT.kGray # HT ds track # HT cross-system track fit if aTrack.GetUniqueID()==15: trackColor = ROOT.kOrange+7 S = aTrack.getFitStatus() @@ -754,7 +779,7 @@ def addTrack(OT,scifi=False): h[ 'simpleDisplay'].Update() nTrack+=1 -def twoTrackEvent(sMin=10,dClMin=7,minDistance=1.5,sepDistance=0.5): +def twoTrackEvent(sMin=10,dClMin=7,minDistance=1.5,sepDistance=0.5, darkMode=False): trackTask.clusScifi.Clear() trackTask.scifiCluster() clusters = trackTask.clusScifi @@ -838,40 +863,40 @@ def twoTrackEvent(sMin=10,dClMin=7,minDistance=1.5,sepDistance=0.5): if len(tracks)==2: OT = sink.GetOutTree() OT.Reco_MuonTracks = tracks - addTrack(OT,True) + addTrack(OT,True, darkMode=darkMode) return passed -def drawDetectors(): - nodes = {'volMuFilter_1/volFeBlockEnd_1':ROOT.kGreen-6} +def drawDetectors(darkMode=False): + nodes = {'volMuFilter_1/volFeBlockEnd_1':ROOT.kGreen-6 if not darkMode else ROOT.kGreen-2} for i in range(mi.NVetoPlanes): nodes['volVeto_1/volVetoPlane_{}_{}'.format(i, i)]=ROOT.kRed for j in range(mi.NVetoBars): if i<2: nodes['volVeto_1/volVetoPlane_{}_{}/volVetoBar_1{}{:0>3d}'.format(i, i, i, j)]=ROOT.kRed if i==2: nodes['volVeto_1/volVetoPlane_{}_{}/volVetoBar_ver_1{}{:0>3d}'.format(i, i, i, j)]=ROOT.kRed - if i<2: nodes['volVeto_1/subVetoBox_{}'.format(i)]=ROOT.kGray+1 - if i==2: nodes['volVeto_1/subVeto3Box_{}'.format(i)]=ROOT.kGray+1 + if i<2: nodes['volVeto_1/subVetoBox_{}'.format(i)]=ROOT.kGray+1 if not darkMode else ROOT.kGray+2 + if i==2: nodes['volVeto_1/subVeto3Box_{}'.format(i)]=ROOT.kGray+1 if not darkMode else ROOT.kGray+2 for i in range(si.nscifi): # number of scifi stations - nodes['volTarget_1/ScifiVolume{}_{}000000'.format(i+1, i+1)]=ROOT.kBlue+1 + nodes['volTarget_1/ScifiVolume{}_{}000000'.format(i+1, i+1)]=ROOT.kBlue+1 if not darkMode else ROOT.kCyan-6 # iron blocks btw SciFi planes in the testbeam 2023-2024 det layout - nodes['volTarget_1/volFeTarget{}_1'.format(i+1)]=ROOT.kGreen-6 + nodes['volTarget_1/volFeTarget{}_1'.format(i+1)]=ROOT.kGreen-6 if not darkMode else ROOT.kGreen-2 for i in range(em.wall): # number of target walls - nodes['volTarget_1/volWallborder_{}'.format(i)]=ROOT.kGray + nodes['volTarget_1/volWallborder_{}'.format(i)]=ROOT.kGray if not darkMode else ROOT.kGray+2 for i in range(mi.NDownstreamPlanes): - nodes['volMuFilter_1/volMuDownstreamDet_{}_{}'.format(i, i+mi.NVetoPlanes+mi.NUpstreamPlanes)]=ROOT.kBlue+1 + nodes['volMuFilter_1/volMuDownstreamDet_{}_{}'.format(i, i+mi.NVetoPlanes+mi.NUpstreamPlanes)]=ROOT.kBlue+1 if not darkMode else ROOT.kCyan-6 for j in range(mi.NDownstreamBars): - nodes['volMuFilter_1/volMuDownstreamDet_{}_{}/volMuDownstreamBar_ver_3{}{:0>3d}'.format(i, i+mi.NVetoPlanes+mi.NUpstreamPlanes, i, j+mi.NDownstreamBars)]=ROOT.kBlue+1 + nodes['volMuFilter_1/volMuDownstreamDet_{}_{}/volMuDownstreamBar_ver_3{}{:0>3d}'.format(i, i+mi.NVetoPlanes+mi.NUpstreamPlanes, i, j+mi.NDownstreamBars)]=ROOT.kBlue+1 if not darkMode else ROOT.kCyan-6 if i < 3: - nodes['volMuFilter_1/volMuDownstreamDet_{}_{}/volMuDownstreamBar_hor_3{}{:0>3d}'.format(i, i+mi.NVetoPlanes+mi.NUpstreamPlanes, i, j)]=ROOT.kBlue+1 + nodes['volMuFilter_1/volMuDownstreamDet_{}_{}/volMuDownstreamBar_hor_3{}{:0>3d}'.format(i, i+mi.NVetoPlanes+mi.NUpstreamPlanes, i, j)]=ROOT.kBlue+1 if not darkMode else ROOT.kCyan-6 for i in range(mi.NDownstreamPlanes): - nodes['volMuFilter_1/subDSBox_{}'.format(i+mi.NVetoPlanes+mi.NUpstreamPlanes)]=ROOT.kGray+1 + nodes['volMuFilter_1/subDSBox_{}'.format(i+mi.NVetoPlanes+mi.NUpstreamPlanes)]=ROOT.kGray+1 if not darkMode else ROOT.kGray+2 for i in range(mi.NUpstreamPlanes): - nodes['volMuFilter_1/subUSBox_{}'.format(i+mi.NVetoPlanes)]=ROOT.kGray+1 - nodes['volMuFilter_1/volMuUpstreamDet_{}_{}'.format(i, i+mi.NVetoPlanes)]=ROOT.kBlue+1 + nodes['volMuFilter_1/subUSBox_{}'.format(i+mi.NVetoPlanes)]=ROOT.kGray+1 if not darkMode else ROOT.kGray+2 + nodes['volMuFilter_1/volMuUpstreamDet_{}_{}'.format(i, i+mi.NVetoPlanes)]=ROOT.kBlue+1 if not darkMode else ROOT.kCyan-6 for j in range(mi.NUpstreamBars): - nodes['volMuFilter_1/volMuUpstreamDet_{}_{}/volMuUpstreamBar_2{}00{}'.format(i, i+mi.NVetoPlanes, i, j)]=ROOT.kBlue+1 - nodes['volMuFilter_1/volFeBlock_{}'.format(i)]=ROOT.kGreen-6 + nodes['volMuFilter_1/volMuUpstreamDet_{}_{}/volMuUpstreamBar_2{}00{}'.format(i, i+mi.NVetoPlanes, i, j)]=ROOT.kBlue+1 if not darkMode else ROOT.kCyan-6 + nodes['volMuFilter_1/volFeBlock_{}'.format(i)]=ROOT.kGreen-6 if not darkMode else ROOT.kGreen-2 for i in range(mi.NVetoPlanes+mi.NUpstreamPlanes,mi.NVetoPlanes+mi.NUpstreamPlanes+mi.NDownstreamPlanes): - nodes['volMuFilter_1/volFeBlock_{}'.format(i)]=ROOT.kGreen-6 + nodes['volMuFilter_1/volFeBlock_{}'.format(i)]=ROOT.kGreen-6 if not darkMode else ROOT.kGreen-2 passNodes = {'Block', 'Wall', 'FeTarget'} xNodes = {'UpstreamBar', 'VetoBar', 'hor'} proj = {'X':0,'Y':1} @@ -1182,12 +1207,12 @@ def dumpChannels(D='Digi_MuFilterHits'): keys.sort() for k in keys: print(text[k]) -def fillNode(node, color=None): +def fillNode(node, color=None, darkMode=False): xNodes = {'UpstreamBar', 'VetoBar', 'hor'} proj = {'X':0,'Y':1} if color == None : - hcal_color = ROOT.kBlack - veto_color = ROOT.kRed+1 + hcal_color = ROOT.kBlack if not darkMode else ROOT.kWhite + veto_color = ROOT.kRed+1 if not darkMode else ROOT.kRed-4 else : hcal_color = color veto_color = color @@ -1210,15 +1235,16 @@ def fillNode(node, color=None): X.Draw('f&&same') X.Draw('same') -def drawInfo(pad, k, run, event, timestamp,moreEventInfo=[]): +def drawInfo(pad, k, run, event, timestamp,moreEventInfo=[], darkMode=False): drawLogo = True drawText = True if drawLogo: padLogo = ROOT.TPad("logo","logo",0.1,0.1,0.2,0.3) padLogo.SetFillStyle(4000) padLogo.SetFillColorAlpha(0, 0) + if darkMode: padLogo.SetFillColor(ROOT.kBlack) padLogo.Draw() - logo = ROOT.TImage.Open('$SNDSW_ROOT/shipLHC/Large__SND_Logo_black_cut.png') + logo = ROOT.TImage.Open('$SNDSW_ROOT/shipLHC/Large__SND_Logo_black_cut.png') if not darkMode else ROOT.TImage.Open('$SNDSW_ROOT/shipLHC/Large__SND_Logo_white_cut.png') logo.SetConstRatio(True) padLogo.cd() logo.Draw() @@ -1239,6 +1265,7 @@ def drawInfo(pad, k, run, event, timestamp,moreEventInfo=[]): textInfo.SetTextAlign(11) textInfo.SetTextFont(42) textInfo.SetTextSize(.15) + if darkMode: textInfo.SetTextColor(ROOT.kWhite) textInfo.DrawLatex(0, 0.6, 'SND@LHC Experiment, CERN') if hasattr(eventTree.EventHeader,'GetEventNumber'): N = eventTree.EventHeader.GetEventNumber() else: N = event @@ -1255,7 +1282,7 @@ def drawInfo(pad, k, run, event, timestamp,moreEventInfo=[]): textInfo.SetTextAlign(11) textInfo.SetTextFont(42) textInfo.SetTextSize(.1) - textInfo.SetTextColor(ROOT.kMagenta+2) + textInfo.SetTextColor((ROOT.kMagenta+2) if not darkMode else (ROOT.kMagenta-4)) dely = 0.12 ystart = 0.85 for i in range(7):