diff --git a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/MakeReferenceFile.cxx b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/MakeReferenceFile.cxx index 29145be9735be41f1a668a090e7d87ad1b05a926..7d284790c4cb0266b8e43731a9118d146f689bc6 100644 --- a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/MakeReferenceFile.cxx +++ b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/MakeReferenceFile.cxx @@ -36,6 +36,8 @@ ATLAS_NO_CHECK_FILE_THREAD_SAFETY; #endif +using iovNamePair= std::pair<std::string, std::string >; + class DbConnection { public: @@ -294,26 +296,36 @@ int main(int argc, char *argv[]) bool useLastIOV = true; cool::IObjectIteratorPtr objectsIterator = f.objectIterator(useLastIOV); // True to use the last IOV - std::vector<std::string> myIOVs; + std::vector< iovNamePair > myIOVs; while (objectsIterator->goToNext()) { const cool::IObject &thisObject = objectsIterator->currentRef(); + std::size_t posPar = iovToString(thisObject).find("]"); + std::size_t posCom = iovToString(thisObject).find(","); + std::string name = iovToString(thisObject).substr(1,posPar-1).replace(posCom-1,1,"_"); std::string display = iovToString(thisObject) + " (" + std::to_string(thisObject.channelId()) + ")\n"; display += payloadToString(thisObject); - myIOVs.push_back(display); + myIOVs.push_back(std::make_pair(name,display)); } - const std::string fileName = tagName + ".log"; - std::ofstream opFile(fileName); if(!useLastIOV){ - // Saving in file the previous to last IOV - testing only so far. - opFile << myIOVs.at(myIOVs.size()-2); + // Saving all the IOVs in different files, just used for experts - Evolution monitoring + for(const auto & [IOVname,iov]:myIOVs){ + std::cout<<IOVname<<"\n"; + const std::string filename= IOVname +"_"+tagName+".log"; + std::ofstream opFile(filename); + opFile <<iov<<"\n"; + opFile.close(); + } } - else{ - opFile << myIOVs.back(); + else{ + const std::string fileName = tagName + ".log"; + std::ofstream opFile(fileName); + opFile << myIOVs.back().second << "\n"; + opFile << std::endl; + opFile.close(); } - opFile << std::endl; - opFile.close(); + return returnCode; } \ No newline at end of file diff --git a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/CheckValues.py b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/CheckValues.py new file mode 100644 index 0000000000000000000000000000000000000000..9818015989b9b9b08fa2334bfb731b34c0e8b480 --- /dev/null +++ b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/CheckValues.py @@ -0,0 +1,143 @@ +# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration + +from PixelCalibAlgs.Recovery import ReadDbFile +from PixelCalibAlgs.EvoMonitoring import ReadCSV +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.figure import Figure +import numpy as np + + +def CalculateTOT(Q,params): + num = params[1] + Q + den = params[2] + Q + if den == 0: + return 0 + return params[0]*(num/den) + +def CheckThresholds(calib): + + import os + os.makedirs("plots/values", exist_ok=True) + + mapping = ReadCSV() + + expectedTOTint = { "Blayer": {"normal":[],"long":[],"ganged":[]}, + "L1" : {"normal":[],"long":[],"ganged":[]}, + "L2" : {"normal":[],"long":[],"ganged":[]}, + "Disk" : {"normal":[],"long":[],"ganged":[]}} + CalibThreshold = { "Blayer": {"normal":[],"long":[],"ganged":[]}, + "L1" : {"normal":[],"long":[],"ganged":[]}, + "L2" : {"normal":[],"long":[],"ganged":[]}, + "Disk" : {"normal":[],"long":[],"ganged":[]}} + CalibRMS = { "Blayer": {"normal":[],"long":[],"ganged":[]}, + "L1" : {"normal":[],"long":[],"ganged":[]}, + "L2" : {"normal":[],"long":[],"ganged":[]}, + "Disk" : {"normal":[],"long":[],"ganged":[]}} + CalibNoise = { "Blayer": {"normal":[],"long":[],"ganged":[]}, + "L1" : {"normal":[],"long":[],"ganged":[]}, + "L2" : {"normal":[],"long":[],"ganged":[]}, + "Disk" : {"normal":[],"long":[],"ganged":[]}} + CalibIntime = { "Blayer": {"normal":[],"long":[],"ganged":[]}, + "L1" : {"normal":[],"long":[],"ganged":[]}, + "L2" : {"normal":[],"long":[],"ganged":[]}, + "Disk" : {"normal":[],"long":[],"ganged":[]}} + + + for mod, FEs in calib.items(): + mod_name = mapping[str(mod)] + mod_layer = "" + if mod_name.startswith("L0"): + mod_layer = "Blayer" + elif mod_name.startswith("L1"): + mod_layer = "L1" + elif mod_name.startswith("L2"): + mod_layer = "L2" + elif mod_name.startswith("D"): + mod_layer = "Disk" + else: + mod_layer = "IBL" + continue + if mod_name.startswith("LI_S15"): + continue + + for fe in FEs: + totint_nor = CalculateTOT(fe[3],fe[12:15]) + totint_lon = CalculateTOT(fe[7],fe[15:18]) + totint_gan = CalculateTOT(fe[11],fe[15:18]) + + expectedTOTint[mod_layer]["normal"].append(totint_nor) + expectedTOTint[mod_layer]["long"].append(totint_lon) + expectedTOTint[mod_layer]["ganged"].append(totint_gan) + + CalibThreshold[mod_layer]["normal"].append(fe[0]) + CalibThreshold[mod_layer]["long"].append(fe[4]) + CalibThreshold[mod_layer]["ganged"].append(fe[8]) + + CalibRMS[mod_layer]["normal"].append(fe[1]) + CalibRMS[mod_layer]["long"].append(fe[5]) + CalibRMS[mod_layer]["ganged"].append(fe[9]) + + CalibNoise[mod_layer]["normal"].append(fe[2]) + CalibNoise[mod_layer]["long"].append(fe[6]) + CalibNoise[mod_layer]["ganged"].append(fe[10]) + + CalibIntime[mod_layer]["normal"].append(fe[3]) + CalibIntime[mod_layer]["long"].append(fe[7]) + CalibIntime[mod_layer]["ganged"].append(fe[11]) + + + + print("\n Threshold values validation:") + print("-"*40) + for i in ["Blayer","L1","L2","Disk"]: + + for j in CalibThreshold[i]: + ValThreshold(i,j,CalibThreshold[i][j]) + figur(i,expectedTOTint, "ToT for intime threshold", "totIntime_"+i+".png") + figur(i,CalibThreshold, "Threshold","CalibThreshold_"+i+".png") + figur(i,CalibRMS , "RMS" ,"CalibRMS_"+i+".png") + figur(i,CalibNoise , "Noise" ,"CalibNoise_"+i+".png") + figur(i,CalibIntime , "Intime" ,"CalibIntime_"+i+".png") + + print("-"*40,"\n") + +def ValThreshold (layer, pix, list): + + # Those values are coming from online crew - Analog thresholds + # https://twiki.cern.ch/twiki/bin/viewauth/Atlas/PixelConditionsRUN3 + realThresholds = { "Blayer": 4700 , "L1": 4300, "L2": 4300, "Disk": 4300} + + listavg = np.average(list) + dev = (realThresholds[layer]-listavg)/listavg*100 + status = "OK" + + # If it deviates more than 1% + if dev > 1: + status = "NEEDS CHECKING!!!" + + print("%-25s: %6.1fe (exp.: %4ue), dev.: %6.2f%% - status (<1%%): %s" % (layer+" avg. thr. ["+pix+"]", listavg, realThresholds[layer], dev, status)) + +def figur(title,hist,xlabel,namef): + fig = Figure(figsize=(13,10)) + fig.suptitle(title) + plot(fig.add_subplot(2,3,1),hist[title]["normal"], xlabel+" - normal") + plot(fig.add_subplot(2,3,2),hist[title]["long"] , xlabel+" - long") + plot(fig.add_subplot(2,3,3),hist[title]["ganged"], xlabel+" - ganged") + plot(fig.add_subplot(2,3,4),hist[title]["normal"], xlabel+" - normal" ,True) + plot(fig.add_subplot(2,3,5),hist[title]["long"] , xlabel+" - long" ,True) + plot(fig.add_subplot(2,3,6),hist[title]["ganged"], xlabel+" - ganged" ,True) + FigureCanvasAgg(fig).print_figure("plots/values/"+namef, dpi=150) + +def plot(axs,arr, xlabel, islog = False): + axs.hist(arr, bins=60) + axs.set_xlabel(xlabel) + if islog: + axs.set_yscale("log") + axs.set_ylabel("Counts [log scale]" if islog else "Counts") + + +if __name__ == "__main__": + + # Used for testing and checking performance + old_calib, old_iov = ReadDbFile("PixelChargeCalibration-DATA-RUN2-UPD4-26.log") + CheckThresholds(old_calib) \ No newline at end of file diff --git a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/EvoMonitoring.py b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/EvoMonitoring.py index 2ba9b07cba717623482b0ed86805be107800f1d0..b16b070641a1a773b033321d34c06141fefd9cbf 100644 --- a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/EvoMonitoring.py +++ b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/EvoMonitoring.py @@ -1,6 +1,6 @@ # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration # -# This script is used to make plots comparing the lastes IOV in the DB with the new calibration. +# This script is used to make plots comparing the latest IOV in the DB with the new calibration. # In order to run it standalone you need to setup Athena and call setupRunning(path_newCalib, path_oldCalib) # oldCalib file has the structure of the Recobery.py (after running the calibration) # newCalib file has the structure of the MakeReferenceFile (It is on IOV from the central DB) @@ -14,8 +14,15 @@ from matplotlib.backends.backend_agg import FigureCanvasAgg from matplotlib.figure import Figure -def arrayCharge(parameters): - m_array = [charge(parameters, 5*(1+i)) for i in range(10)] +def arrayCharge(parameters, layer): + + if layer == "Blayer": + # Range [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42] (includes the ToT tuning point: 18) + m_array = [charge(parameters, 3*(1+i)) for i in range(14)] + else: + # Range [5, 10, 15, 20, 25, 30, 35, 40, 45, 50] (includes the ToT tuning point: 30) + m_array = [charge(parameters, 5*(1+i)) for i in range(8)] + return ar.array('d',m_array) def charge(parameters, tot): @@ -25,13 +32,38 @@ def charge(parameters, tot): def percent( a,b): return a/(a+b)*100 if (a+b) != 0 else 999.99 + + +def failsCheckTuning(parameters, layer): + tot = 30 + error = 5 # percentage + expected_charge = 20000 # electrons + + if layer == "Blayer": + tot = 18 + + if layer == "IBL": + tot = 10 + expected_charge = 16000 # electrons + + if abs(parameters[9]-expected_charge)/expected_charge*100 > 5: + return True, [parameters[9], abs(parameters[9]-expected_charge)/expected_charge*100, expected_charge] + return False, [0,0,0] + + low = expected_charge*(1-error/100) + high = expected_charge*(1+error/100) + realQ = charge(parameters, tot) + + if realQ < low or realQ > high: + return True, [charge(parameters, tot), abs(charge(parameters, tot)-expected_charge)/expected_charge*100,expected_charge] + return False, [0,0,0] def EvoMon(old_calib, new_calib, mapping, old_iov, new_iov): save = 1 log_info = {} - plot_range = [0,75000] + plot_range = np.array([0,75000]) slopes = [] information = {"Total_mods": 0, @@ -53,12 +85,16 @@ def EvoMon(old_calib, new_calib, mapping, old_iov, new_iov): mod_layer = "Blayer" elif mod_str.startswith("L1"): mod_layer = "L1" + continue elif mod_str.startswith("L2"): mod_layer = "L2" + continue elif mod_str.startswith("D"): mod_layer = "Disk" + continue else: mod_layer = "IBL" + continue if mod_str.startswith("LI_S15"): continue @@ -71,33 +107,49 @@ def EvoMon(old_calib, new_calib, mapping, old_iov, new_iov): information["Total_FE"] += 1 newQ = [] oldQ = [] + boolTOT = False + realQ = [] if mod_layer != "IBL": newCal_normal_pix = new_calib[str(mod)][fe][12:15] oldCal_normal_pix = old_calib[str(mod)][fe][12:15] + boolTOT, realQ = failsCheckTuning(newCal_normal_pix, mod_layer) - # We just fet the first point since we loose linearity afetrwards - newQ = arrayCharge(newCal_normal_pix)[:8] - oldQ = arrayCharge(oldCal_normal_pix)[:8] + # We just fet the first point since we loose linearity afterwards + newQ = arrayCharge(newCal_normal_pix,mod_layer) + oldQ = arrayCharge(oldCal_normal_pix,mod_layer) else: # For IBL we dont need to convert TOT into charge, DB already in charge newQ = new_calib[str(mod)][fe][4:20] oldQ = old_calib[str(mod)][fe][4:20] - plot_range = [0,35000] - - + plot_range = np.array([0,35000]) + boolTOT, realQ = failsCheckTuning(newQ, mod_layer) + m,b = np.polyfit(newQ ,oldQ,1) slopes.append(m) + boolFit = (abs((1-m)/m)*100) > 5 - if (abs((1-m)/m)*100) > 5: + if boolFit or boolTOT: key = "%-18s - %i" % (mod_str, mod) - if key not in log_info: - log_info[key] = "\tFE%02i ---> slope: %5.2f - deviation: %5.1f%%\n" % (fe,m, abs((1-m)/m)*100) - else: - log_info[key] += "\tFE%02i ---> slope: %5.2f - deviation: %5.1f%%\n" % (fe,m, abs((1-m)/m)*100) + if boolFit: + if key not in log_info: + log_info[key] = "\tFE%02i ---> slope: %5.2f - dev: %5.1f%%\n" % (fe,m, abs((1-m)/m)*100) + else: + log_info[key] += "\tFE%02i ---> slope: %5.2f - dev: %5.1f%%\n" % (fe,m, abs((1-m)/m)*100) + if boolTOT: + if key not in log_info: + log_info[key] = "\tFE%02i ---> Charge= %5ie (dev %6.2f%%) out of error bars. Expected: %5ie\n" % (fe, realQ[0], realQ[1], realQ[2]) + else: + log_info[key] += "\tFE%02i ---> Charge= %5ie (dev %6.2f%%) out of error bars. Expected: %5ie\n" % (fe, realQ[0], realQ[1], realQ[2]) information[mod_layer]["bad"] += 1 - status = "_BAD" + status = "_BAD" + if boolTOT: + # Fails charge tuning + status += "_Q" + if boolFit: + # Fails the slope at y = x (old vs. new calib) + status += "_Slope" else: information[mod_layer]["ok"] += 1 status = "_OK" @@ -129,14 +181,13 @@ def EvoMon(old_calib, new_calib, mapping, old_iov, new_iov): storage = "plots/" + mod_layer + "/" canvas = FigureCanvasAgg(fig) - canvas.print_figure(storage+mod_str+"_id"+str(mod)+status+".png", dpi=150) + canvas.print_figure(storage+"Id"+str(mod)+ "_" +mod_str+status+".png", dpi=150) if(save): fig = Figure(figsize=(13,10)) fig.suptitle("All modules") axs = fig.add_subplot(1,1,1) axs.hist(np.clip(slopes, -1, 1.49), bins=100) - axs.set_yscale("log") axs.set_xlabel("Fit slope") axs.set_ylabel("Counts") FigureCanvasAgg(fig).print_figure("plots/slopes.png", dpi=150) @@ -161,6 +212,10 @@ def EvoMon(old_calib, new_calib, mapping, old_iov, new_iov): print("+"*20+" List of bad FE "+"+"*20 ) + print("Expected TOT vs. charge for the different layers:") + print("%-10s: TOT@%2i = %2ike" % ("IBL" , 10, 16)) + print("%-10s: TOT@%2i = %2ike" % ("Blayer" , 18, 20)) + print("%-10s: TOT@%2i = %2ike\n" % ("L1/L2/Disk", 30, 20)) for key, val in log_info.items(): print(key) print(val) @@ -224,8 +279,8 @@ if __name__ == "__main__": description="""Compares two IOV and plots the results.\n\n Example: python -m PixelCalibAlgs.EvoMonitoring --new "path/to/file" --old "path/to/file" """) - parser.add_argument('--new', required=True, default="FINAL_calibration_candidate.txt", help="New calibration file (output format from the Recovery.py)") - parser.add_argument('--old', required=True, default="PixelChargeCalibration-DATA-RUN2-UPD4-26.log", help="Old DB IOV calibration") + parser.add_argument('--new', default="FINAL_calibration_candidate.txt", help="New calibration file (output format from the Recovery.py)") + parser.add_argument('--old', default="PixelChargeCalibration-DATA-RUN2-UPD4-26.log", help="Old DB IOV calibration") args = parser.parse_args() setupRunEvo(args.new, args.old) diff --git a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/Recovery.py b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/Recovery.py index b0316bcc268eec0c9879a7fdec452181ea4ce857..f96cb368013162dd6230255a3b8dbf2545e80659 100644 --- a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/Recovery.py +++ b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/python/Recovery.py @@ -164,6 +164,10 @@ def recover_empties(new_calib, ref_calib): def UpdateAndSave(new_calib, ref_calib): + # Validation for the new calibration (checks the threshold, RMS, Noise and Intime thresholds) + from PixelCalibAlgs.CheckValues import CheckThresholds + CheckThresholds(new_calib) + # Making a copy of the reference calibration updated_calib = ref_calib.copy() @@ -207,8 +211,10 @@ def UpdateCalib(tag): new_calib, read_report = ReadNewCalib("calibration_merged.txt") # modifying the new_calib dictionary in order to recover the empty FE + print("Recovering missing information..") report, counter_report = recover_empties(new_calib,ref_calib) + print("Validating and updating reference calibration.. ") UpdateAndSave(new_calib,ref_calib) f = open("log_recovery.txt", "w") diff --git a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/tools/Calib.cxx b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/tools/Calib.cxx index 631415b04d5d01ee83aac79bbaff81b1c5c01f70..9b8533a4a2ea5050ab0831d7e34abcf47294f869 100644 --- a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/tools/Calib.cxx +++ b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/ChargeCalibration/pixel/tools/Calib.cxx @@ -588,11 +588,11 @@ bool Calib::fillThresholds(const pix::PixelMapping &pm, const std::string &inThr continue; } + // pixel discriminator threshold std::unique_ptr<TH2F> h2dThr(get2DHistogramFromPath(rodDir,modName, "SCURVE_MEAN")); h2dThr->SetDirectory(0); - - // Gettting histogram for noise + // Getting histogram for noise std::unique_ptr<TH2F>h2dSig(get2DHistogramFromPath(rodDir,modName, "SCURVE_SIGMA")); h2dSig->SetDirectory(0); diff --git a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/python/PixelCalibrationConfig.py b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/python/PixelCalibrationConfig.py index 3daa028ad65906395312063fe96e7c9c44af7555..10901c37cba6b83de9b96495a2beb273c1c8d85f 100644 --- a/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/python/PixelCalibrationConfig.py +++ b/InnerDetector/InDetCalibAlgs/PixelCalibAlgs/python/PixelCalibrationConfig.py @@ -12,11 +12,12 @@ if __name__=="__main__": parser.add_argument('--thr' , required=True, help="Threshold file, format must be \"SCAN_SXXXXXXXXX\" ") parser.add_argument('--thr_intime', required=True, help="Threshold intime file, format must be \"SCAN_SXXXXXXXXX\" ") parser.add_argument('--tot' , required=True, help="Time over threshold file, format must be \"SCAN_SXXXXXXXXX\" ") - parser.add_argument('--layers' , required=True, nargs='+', choices={"Blayer","L1","L2","disk"}, help="What layers we should run to update the calibration.") + parser.add_argument('--layers' , required=True, nargs='+', choices={"Blayer","L1","L2","disk"}, help="Layers we should run to update the calibration.") + parser.add_argument('--tag' , type=str, default="PixelChargeCalibration-DATA-RUN2-UPD4-26", help="Tag in order to read the DB") parser.add_argument('--saveInfo' , action='store_true', help="Creates a root file with the fitting plots - Slower running time") parser.add_argument('--runCal' , action='store_true', help="Runs only the Pixel Calibration layers") - parser.add_argument('--skipPlots' , action='store_true', help="Skips the plotting step. Takes less time") - parser.add_argument('--tag' , type=str, default="PixelChargeCalibration-DATA-RUN2-UPD4-26", help="Tag in order to read the DB") + parser.add_argument('--skipPlots' , action='store_true', help="Skips the plotting step - Slower running time") + args = parser.parse_args()