Skip to content

Commit

Permalink
Merge pull request #45975 from amecca/improve-PixelBarycentre
Browse files Browse the repository at this point in the history
Porting the Pixel Barycentre tool in the All-in-One framework
  • Loading branch information
cmsbuild authored Oct 18, 2024
2 parents 29c3108 + ae31a0f commit a503731
Show file tree
Hide file tree
Showing 13 changed files with 503 additions and 163 deletions.
22 changes: 22 additions & 0 deletions Alignment/OfflineValidation/README_PixBary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Pixel Barycentre Validation
Currently only the `single` type of job is implemented in the All-in-One Tool, but the execution of the plotting script `plotBaryCentre_VS_BeamSpot.py` could be added in the future as a `trend` job.

## Extraction from TrackerAlignmentRcd - PixelBarycentreAnalyzer_cfg.py
This config runs `PixelBaryCentreAnalyzer` on an empty source, after configuring the GlobalTag and additional conditions for a given alignment.
The following parameters are expected/accepted in the json/yaml configuration file:
```
validations:
PixBary:
single:
<job_name>:
<options>
```

The following options are understood:

Variable | Default value | Explanation/Options
-------- | ------------- | --------------------
firstRun | 290550 | The first run to process (inclusive)
lastRun | 325175 | The last run to process (inclusive)
lumisPerRun | 1 | The number of LumiSections tested for a change in the TrackerAlignmentRcd in each run
alignments | None | List of alignments for which this validation is run
40 changes: 21 additions & 19 deletions Alignment/OfflineValidation/plugins/PixelBaryCentreAnalyzer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
* Python script plotBaryCentre_VS_BeamSpot.py under script dir is used to plot barycentres from alignment constants used in Prompt-Reco, End-of-Year Rereco and so-called Run-2 (Ultra)Legacy Rereco. Options of the plotting script can be found from the helper in the script.
*
* $Date: 2021/01/05 $
* $Revision: 1.0 $
* $Last Modified: 2024/09/23 $
* $Revision: 1.1 $
* \author Tongguang Cheng - Beihang University <[email protected]>
* \author Alberto Mecca - Torino University <[email protected]>
*
*/

Expand Down Expand Up @@ -246,32 +248,32 @@ void PixelBaryCentreAnalyzer::analyze(const edm::Event& iEvent, const edm::Event

phase_ = -1;

const TrackerGeometry* tkGeo = &iSetup.getData(trackerGeometryToken_);
const TrackerTopology* tkTopo = &iSetup.getData(trackerTopologyToken_);
const TrackerGeometry& tkGeom = iSetup.getData(trackerGeometryToken_);
const TrackerTopology& tkTopo = iSetup.getData(trackerTopologyToken_);

if (tkGeo->isThere(GeomDetEnumerators::PixelBarrel) && tkGeo->isThere(GeomDetEnumerators::PixelEndcap)) {
if (tkGeom.isThere(GeomDetEnumerators::PixelBarrel) && tkGeom.isThere(GeomDetEnumerators::PixelEndcap)) {
phase_ = 0;
} else if (tkGeo->isThere(GeomDetEnumerators::P1PXB) && tkGeo->isThere(GeomDetEnumerators::P1PXEC)) {
} else if (tkGeom.isThere(GeomDetEnumerators::P1PXB) && tkGeom.isThere(GeomDetEnumerators::P1PXEC)) {
phase_ = 1;
}

// pixel quality
const SiPixelQuality* badPixelInfo = &iSetup.getData(siPixelQualityToken_);
const SiPixelQuality& badPixelInfo = iSetup.getData(siPixelQualityToken_);

// Tracker global position
const AlignTransform glbCoord = align::DetectorGlobalPosition(iSetup.getData(gprToken_), DetId(DetId::Tracker));

// Convert AlignTransform::Translation to GlobalVector using the appropriate constructor
GlobalVector globalTkPosition(glbCoord.translation().x(), glbCoord.translation().y(), glbCoord.translation().z());
const Alignments& globalAlignments = iSetup.getData(gprToken_);
const AlignTransform& globalCoordinates = align::DetectorGlobalPosition(globalAlignments, DetId(DetId::Tracker));
GlobalVector globalTkPosition(
globalCoordinates.translation().x(), globalCoordinates.translation().y(), globalCoordinates.translation().z());

// loop over bclabels
for (const auto& label : bcLabels_) {
// init tree content
PixelBaryCentreAnalyzer::initBC();

// Get TkAlign from EventSetup:
const Alignments* alignments = &iSetup.getData(tkAlignTokens_[label]);
std::vector<AlignTransform> tkAlignments = alignments->m_align;
const Alignments& alignments = iSetup.getData(tkAlignTokens_[label]);
std::vector<AlignTransform> tkAlignments = alignments.m_align;

// PIX
GlobalVector barycentre_PIX(0.0, 0.0, 0.0);
Expand All @@ -295,7 +297,7 @@ void PixelBaryCentreAnalyzer::analyze(const edm::Event& iEvent, const edm::Event
//DetId
const DetId& detId = DetId(ali.rawId());
// remove bad module
if (usePixelQuality_ && badPixelInfo->IsModuleBad(detId))
if (usePixelQuality_ && badPixelInfo.IsModuleBad(detId))
continue;

// alignment for a given module
Expand All @@ -308,8 +310,8 @@ void PixelBaryCentreAnalyzer::analyze(const edm::Event& iEvent, const edm::Event
barycentre_BPIX += ali_translation;
barycentre_PIX += ali_translation;

int layer = tkTopo->pxbLayer(detId);
int ladder = tkTopo->pxbLadder(detId);
int layer = tkTopo.pxbLayer(detId);
int ladder = tkTopo.pxbLadder(detId);
nmodules_bpix[layer][ladder] += 1;
barycentre_bpix[layer][ladder] += ali_translation;

Expand All @@ -321,16 +323,16 @@ void PixelBaryCentreAnalyzer::analyze(const edm::Event& iEvent, const edm::Event
barycentre_FPIX += ali_translation;
barycentre_PIX += ali_translation;

int disk = tkTopo->pxfDisk(detId);
int quadrant = PixelEndcapName(detId, tkTopo, phase_).halfCylinder();
int disk = tkTopo.pxfDisk(detId);
int quadrant = PixelEndcapName(detId, &tkTopo, phase_).halfCylinder();
if (quadrant < 3)
disk *= -1;

int ring = -9999;
if (phase_ == 0) {
ring = 1 + (tkTopo->pxfPanel(detId) + tkTopo->pxfModule(detId.rawId()) > 3);
ring = 1 + (tkTopo.pxfPanel(detId) + tkTopo.pxfModule(detId.rawId()) > 3);
} else if (phase_ == 1) {
ring = PixelEndcapName(detId, tkTopo, phase_).ringName();
ring = PixelEndcapName(detId, &tkTopo, phase_).ringName();
}

nmodules_fpix[disk][ring] += 1;
Expand Down
64 changes: 64 additions & 0 deletions Alignment/OfflineValidation/python/TkAlAllInOneTool/PixBary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
import copy

_validationName = "PixBary"

def PixBary(config, validationDir, verbose=False):
##List with all jobs
jobs = []

##Dictionary of lists of all IOVs (can be different per each single job)
IOVs = {}

##Start with single jobs
jobType = "single"

##Check that a job is defined
if not jobType in config["validations"][_validationName]:
raise LookupError("No '%s' key word in config for %s" %(jobType, _validationName))

##Loop over all merge jobs/IOVs which are requested
for jobName, jobConfig in config["validations"][_validationName][jobType].items():
IOV_list = get_IOVs(jobConfig) # The PixelBarycentre automatically detects IOV changes in the payloads. This list is used to specify the run range(s) to analyze
if(verbose):
print('job: %s IOV_list: %s', jobName, IOV_list)
IOVs[jobName] = IOV_list

##Loop over IOVs (ranges of runs, in this case)
for runRange in IOV_list:
IOV = '-'.join(str(i) for i in runRange)

for alignment, alignmentConfig in config["alignments"].items():
##Work directory for each IOV
workDir = os.path.join(validationDir, _validationName, jobType, jobName, alignment, IOV)

##Write local config
local = {}
local["output"] = os.path.join(config["LFS"], config["name"], _validationName, jobType, alignment, jobName, IOV)
local["alignment"] = copy.deepcopy(alignmentConfig)
local["alignment"]["label"] = alignment
local["validation"] = copy.deepcopy(jobConfig)
local["validation"].pop("alignments")
local["validation"]["IOV"] = IOV
if "dataset" in local["validation"]:
local["validation"]["dataset"] = local["validation"]["dataset"].format(IOV)
if "goodlumi" in local["validation"]:
local["validation"]["goodlumi"] = local["validation"]["goodlumi"].format(IOV)

##Write job info
job = {
"name": "{}_{}_{}_{}_{}".format(_validationName, alignment, jobType, jobName, IOV),
"dir": workDir,
"exe": "cmsRun",
"cms-config": "{}/src/Alignment/OfflineValidation/python/TkAlAllInOneTool/PixelBaryCentreAnalyzer_cfg.py".format(os.environ["CMSSW_BASE"]),
"run-mode": "Condor",
"dependencies": [],
"config": local,
}

jobs.append(job)

return jobs

def get_IOVs(jobConfig):
return [[jobConfig['firstRun'], jobConfig['lastRun']]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import FWCore.ParameterSet.Config as cms
import os
import json

process = cms.Process("READ")

# import of standard configurations
process.load('Configuration.StandardSequences.Services_cff')
process.load('Configuration.EventContent.EventContent_cff')
process.load('Configuration.StandardSequences.GeometryRecoDB_cff')
process.load('Configuration.StandardSequences.MagneticField_AutoFromDBCurrent_cff')
process.load('Configuration.StandardSequences.FrontierConditions_GlobalTag_cff')

import FWCore.ParameterSet.VarParsing as VarParsing
from Configuration.AlCa.GlobalTag import GlobalTag

options = VarParsing.VarParsing()
options.register('lumisPerRun',
1,
VarParsing.VarParsing.multiplicity.singleton,
VarParsing.VarParsing.varType.int,
"the number of lumis to be processed per-run.")
options.register('firstRun',
290550,
VarParsing.VarParsing.multiplicity.singleton,
VarParsing.VarParsing.varType.int,
"the first run number be processed")
options.register('lastRun',
325175,
VarParsing.VarParsing.multiplicity.singleton,
VarParsing.VarParsing.varType.int,
"the run number to stop at")
options.register('config',
default = None,
mult = VarParsing.VarParsing.multiplicity.singleton,
mytype = VarParsing.VarParsing.varType.string,
info = 'JSON config with information about the GT, Alignments, etc.')
options.register('unitTest',
False, # default value
VarParsing.VarParsing.multiplicity.singleton, # singleton or list
VarParsing.VarParsing.varType.bool, # string, int, or float
"is it a unit test?")

defaultFirstRun = options.firstRun
defaultLastRun = options.lastRun
defaultLumisPerRun = options.lumisPerRun

options.parseArguments()

if(options.config is None):
configuration = {
"alignments": {
"prompt": {
"globaltag": "140X_dataRun3_Prompt_v4",
"conditions": {"TrackerAlignmentRcd": {"tag":"TrackerAlignment_PCL_byRun_v2_express"}}
},
"EOY": {
"conditions": {"TrackerAlignmentRcd": {"tag":"TrackerAlignment_v24_offline"}}
},
"rereco": {
"conditions": {"TrackerAlignmentRcd": {"tag":"TrackerAlignment_v29_offline"}}
}
},
"validation": {}
}
else:
# Load configuration from file
with open(options.config) as f:
configuration = json.load(f)

# The priority for the options is:
# 1. Value specified on command line
# 2. Value in the config
# 3. Default value in the parser
if(options.firstRun != defaultFirstRun):
firstRun = options.firstRun
else:
firstRun = configuration["validation"].get('firstRun', defaultFirstRun)

if(options.lastRun != defaultLastRun):
lastRun = options.lastRun
else:
lastRun = configuration["validation"].get('lastRun', defaultLastRun)

if(options.lumisPerRun != defaultLumisPerRun):
lumisPerRun = options.lumisPerRun
else:
lumisPerRun = configuration["validation"].get('lumisPerRun', defaultLumisPerRun)

process.load("FWCore.MessageService.MessageLogger_cfi")

# Test that the configuration is complete
if(lastRun < firstRun):
raise ValueError("The last run is smaller than the first")

process.MessageLogger.cerr.FwkReport.reportEvery = lumisPerRun*1000 # do not clog output with I/O

if options.unitTest:
numberOfRuns = 10
else:
numberOfRuns = lastRun - firstRun + 1

print("INFO: Runs: {:d} - {:d} --> number of runs: {:d}".format(firstRun, lastRun, numberOfRuns))

process.maxEvents = cms.untracked.PSet( input = cms.untracked.int32(options.lumisPerRun*numberOfRuns) )

####################################################################
# Empty source
####################################################################
#import FWCore.PythonUtilities.LumiList as LumiList
#DCSJson='/afs/cern.ch/cms/CAF/CMSCOMM/COMM_DQM/certification/Collisions16/13TeV/DCSOnly/json_DCSONLY.txt'

process.source = cms.Source("EmptySource",
firstRun = cms.untracked.uint32(firstRun),
firstLuminosityBlock = cms.untracked.uint32(1), # probe one LS after the other
numberEventsInLuminosityBlock = cms.untracked.uint32(1), # probe one event per LS
numberEventsInRun = cms.untracked.uint32(lumisPerRun), # a number of events > the number of LS possible in a real run (5000 s ~ 32 h)
)

####################################################################
# Load and configure analyzer
####################################################################
bcLabels_ = cms.untracked.vstring("")
bsLabels_ = cms.untracked.vstring("")

alignments = configuration.get('alignments', None) # NOTE: aligments is plural
if alignments is None:
align = configuration['alignment'] # NOTE: alignment is singular
label = configuration['alignment'].get('label', align['title'].split()[0])
alignments = {label: align}

for label, align in alignments.items():
if(align.get('globaltag')):
if(len(process.GlobalTag.globaltag.value()) > 0):
if(process.GlobalTag.globaltag.value() != align['globaltag']):
print('ERROR: another GT has already been specified: "{}". Ignoring GT "{}" from alignment "{}"'.format(
process.GlobalTag.globaltag.value(), align['globaltag'], label))
else:
# Assign this GlobalTag to the process
process.GlobalTag = GlobalTag(process.GlobalTag, align['globaltag'])
print('INFO: GlobalTag:', process.GlobalTag.globaltag.value())

conditions = align.get('conditions')
if(conditions is None):
print('INFO: No conditions specified for alignment "{}": skipping'.format(label))
continue

bcLabels_.append(label)
print(f'TrackerAlignment: {label=} {align=}')

for record, condition in conditions.items():
condition.setdefault('connect', 'frontier://FrontierProd/CMS_CONDITIONS')
if (record == 'TrackerAlignmentRcd'):
condition.setdefault('tag', 'Alignments')
elif(record == 'TrackerSurfaceDeformationRcd'):
condition.setdefault('tag', 'Deformations')
elif(record == 'TrackerAlignmentErrorsExtendedRcd'): # Errors should not affect the barycentre
condition.setdefault('tag', 'AlignmentErrors')

process.GlobalTag.toGet.append(
cms.PSet(
record = cms.string(record),
label = cms.untracked.string(label),
tag = cms.string(condition['tag']),
connect = cms.string(condition['connect'])
)
)


for label, beamspot in configuration['validation'].get("beamspots", {}).items() :
bsLabels_.append(label)
print(f'BeamSpot : {label=} {beamspot=}')

process.GlobalTag.toGet.append(
cms.PSet(
record = cms.string("BeamSpotObjectsRcd"),
label = cms.untracked.string(label),
tag = cms.string(beamspot["tag"]),
connect = cms.string(beamspot.get("connect", "frontier://FrontierProd/CMS_CONDITIONS"))
)
)


from Alignment.OfflineValidation.pixelBaryCentreAnalyzer_cfi import pixelBaryCentreAnalyzer as _pixelBaryCentreAnalyzer

process.PixelBaryCentreAnalyzer = _pixelBaryCentreAnalyzer.clone(
usePixelQuality = False,
tkAlignLabels = bcLabels_,
beamSpotLabels = bsLabels_
)

process.PixelBaryCentreAnalyzerWithPixelQuality = _pixelBaryCentreAnalyzer.clone(
usePixelQuality = True,
tkAlignLabels = bcLabels_,
beamSpotLabels = bsLabels_
)

####################################################################
# Output file
####################################################################
outfile = os.path.join(configuration.get("output", os.getcwd()), 'PixelBaryCentre.root')

process.TFileService = cms.Service("TFileService",
fileName=cms.string(outfile)
)
print('INFO: output in', outfile)

# Put module in path:
process.p = cms.Path(process.PixelBaryCentreAnalyzer
#*process.PixelBaryCentreAnalyzerWithPixelQuality
)
Loading

0 comments on commit a503731

Please sign in to comment.