Calibrators#
The PocketCoffea framework provides a flexible and powerful calibration system to handle object corrections and systematic variations in CMS analyses. The calibration system is designed around the concept of Calibrators - modular components that apply corrections to physics objects (jets, electrons, muons, MET, etc.) and manage their systematic variations.
Overview#
The calibration system consists of three main components:
Calibrator: Abstract base class that defines individual calibration steps
CalibratorsManager: Orchestrates the application of multiple calibrators in sequence
Base Workflow Integration: Automatic handling of systematic variations in the analysis workflow
Key Features#
Sequential Processing: Calibrators are applied in a user-defined sequence, allowing for complex interdependencies
Automatic Variation Handling: Each calibrator can define its own systematic variations that are automatically propagated through the analysis and made available in the configuration file.
Original Collection Preservation: The system maintains references to original collections for calibrators that need uncorrected inputs
Flexible Configuration: Calibrators can be configured through parameters and enabled/disabled per data-taking period. Also the systematic variations can be different by period or event type (Sample).
Type Safety: Built-in checks ensure calibrators only modify collections they declare to handle
Calibrator Base Class#
All calibrators inherit from the abstract Calibrator class and must implement specific methods:
Class Attributes#
1class YourCalibrator(Calibrator):
2 name: str = "your_calibrator_name" # Unique identifier
3 has_variations: bool = True # Whether this calibrator provides variations
4 isMC_only: bool = False # Whether to run only on MC
5 calibrated_collections: List[str] = [
6 "Collection.field"
7 ] # Collections this calibrator modifies
Required Methods#
Constructor __init__(self, params, metadata, do_variations, **kwargs)#
Called to initialize the Calibrator and store necessary metadata for easy later usage. do_variations tells the calibrator whether its variations are requested for the current chunk (set by the CalibratorsManager, based on the configuration).
initialize(events)#
Called once per chunk to prepare calibration data:
1def initialize(self, events):
2 # Prepare calibration factors, load correction files
3 # Set up variations list: self._variations = ["variation1Up", "variation1Down", ...]
4 pass
This method should setup the self._variations variable to define dynamically the list of variations made available for
the current chunk of events. Both the Up and Down variations should be defined (meaning that the framework does not assume any variation automatically).
calibrate(events, events_original_collections, variation, already_applied_calibrators)#
Called for each systematic variation to apply corrections:
1def calibrate(
2 self,
3 events,
4 events_original_collections,
5 variation,
6 already_applied_calibrators=None,
7):
8 # Apply corrections based on the requested variation
9 # Return dictionary: {"Collection.field": corrected_values}
10 return {"Jet.pt": corrected_jet_pts}
Built-in Calibrators#
PocketCoffea provides several ready-to-use calibrators in pocket_coffea.lib.calibrators.common:
JetsCalibrator#
Name:
"jet_calibration"Purpose: Applies Jet Energy Corrections (JEC) and Jet Energy Resolution (JER) smearing. If pT regression is requested for a jet type (
apply_pt_regr_MC/apply_pt_regr_Data), it is applied first, before the JEC.Collections: Configurable. Every jet collection listed in
jets_calibration.collection[year](e.g.Jet,FatJet) that hasapply_jec_MC/apply_jec_Dataenabled for its jet type is calibrated.Variations: One
"{jet_type}_{source}Up"/"{jet_type}_{source}Down"pair per entry configured injets_calibration.variations[jet_type][year](e.g."AK4PFchs_jecUp","AK4PFchs_jerDown").
JetsSoftdropMassCalibrator#
Name:
"msoftdrop_calibration"Purpose: Applies the JEC to the softdrop mass of AK8 (
AK8PFPuppi) jets, using the corresponding AK4 subjet corrections.Collections:
["FatJet.msoftdrop"]Variations: Not yet implemented for the softdrop mass (only the nominal correction is available).
Note: Not part of
default_calibrators_sequence; add it explicitly to your calibrator sequence if softdrop mass calibration is needed.
METCalibrator#
Name:
"met_type1_calibration"Purpose: Recomputes Type-1 corrected MET starting from the raw MET, propagating the jet corrections and, if they ran earlier in the sequence, the electron/muon scale and smearing corrections.
Collections:
["MET.pt", "MET.phi"]or["PuppiMET.pt", "PuppiMET.phi"], depending onmet_calibration.MET_collectionVariations:
"unclust_EnUp","unclust_EnDown"(unclustered energy variations)Dependencies: Must run after
JetsCalibrator, and after any electron/muon calibrators whose corrections should be propagated to the MET (it compares the original and calibratedElectron.pt/Muon.ptto do so)
ElectronsScaleCalibrator#
Name:
"electron_scale_and_smearing"Purpose: Applies electron energy scale (data) and resolution smearing (MC) corrections
Collections:
["Electron.pt", "Electron.pt_original"]Variations:
"ele_scaleUp/Down","ele_smearUp/Down"(MC only)
MuonsCalibrator#
Name:
"muons_scale_and_resolution"Purpose: Applies muon momentum scale and resolution corrections
Collections:
["Muon.pt", "Muon.pt_original", "Muon.energyErr"]Variations:
"muon_scaleUp/Down","muon_smearUp/Down"(MC only)
default_calibrators_sequence runs these in the order [JetsCalibrator, ElectronsScaleCalibrator, MuonsCalibrator, METCalibrator] (JetsSoftdropMassCalibrator is not included).
Configuration#
Basic Setup#
In your analysis configuration file:
1from pocket_coffea.lib.calibrators.common import default_calibrators_sequence
2
3cfg = Configurator(
4 # ... other configuration ...
5 # Use default calibrator sequence
6 calibrators=default_calibrators_sequence,
7 # Configure shape variations
8 variations={
9 "shape": {
10 "common": {
11 "inclusive": ["jet_calibration"], # Run jet variations for all samples
12 },
13 "bysample": {
14 "MC_Sample": {
15 "inclusive": [
16 "electron_scale_and_smearing"
17 ], # Run electron variations for specific samples
18 }
19 },
20 }
21 },
22)
Custom Calibrator Sequence#
You can define your own calibrator sequence:
1from pocket_coffea.lib.calibrators.common import JetsCalibrator, METCalibrator
2from your_module import CustomCalibrator
3
4custom_sequence = [JetsCalibrator, METCalibrator, CustomCalibrator]
5
6cfg = Configurator(
7 calibrators=custom_sequence,
8 # ... rest of configuration
9)
Parameters Configuration#
Calibrators read their configuration from the parameters system. Example for jet calibration:
# params/jets_calibration.yaml
jets_calibration:
collection:
2022:
AK4PFchs: "Jet"
AK8PFPuppi: "FatJet"
jet_types:
AK4PFchs:
2016_PreVFP:
json_path: ${cvmfs:Run2-2016preVFP-UL-NanoAODv9,JME,jet_jerc.json.gz}
jec_mc: Summer19UL16APV_V7_MC
jec_data:
B: Summer19UL16APV_RunBCD_V7_DATA
C: Summer19UL16APV_RunBCD_V7_DATA
D: Summer19UL16APV_RunBCD_V7_DATA
E: Summer19UL16APV_RunEF_V7_DATA
F: Summer19UL16APV_RunEF_V7_DATA
jer: Summer20UL16APV_JRV3_MC
level: L1L2L3Res
...
apply_jec_MC:
2022:
AK4PFchs: true
AK8PFPuppi: true
apply_jec_Data:
2022:
AK4PFchs: true
AK8PFPuppi: false
variations:
2022:
AK4PFchs: ["jec", "jer"]
AK8PFPuppi: ["jec"]
Creating Custom Calibrators#
Simple Example#
Here’s a template for a custom calibrator:
1from pocket_coffea.lib.calibrators.calibrator import Calibrator
2import awkward as ak
3
4
5class MyCustomCalibrator(Calibrator):
6 name = "my_custom_calibrator"
7 has_variations = True
8 isMC_only = False
9 calibrated_collections = ["MyObject.pt", "MyObject.mass"]
10
11 def __init__(self, params, metadata, do_variations, **kwargs):
12 super().__init__(params, metadata, do_variations, **kwargs)
13 # Access configuration
14 self.my_config = self.params.my_calibrator_config
15
16 def initialize(self, events):
17 # Prepare correction factors
18 self.scale_factor = self.calculate_scale_factor(events)
19
20 # Define available variations
21 if self.isMC:
22 self._variations = ["myUncertaintyUp", "myUncertaintyDown"]
23 else:
24 self._variations = []
25
26 def calibrate(
27 self, events, orig_colls, variation, already_applied_calibrators=None
28 ):
29 # Get the objects to calibrate
30 objects = events["MyObject"]
31
32 # Apply nominal correction
33 corrected_pt = objects.pt * self.scale_factor
34 corrected_mass = objects.mass * self.scale_factor
35
36 # Apply systematic variations
37 if variation == "myUncertaintyUp":
38 corrected_pt = corrected_pt * 1.02
39 elif variation == "myUncertaintyDown":
40 corrected_pt = corrected_pt * 0.98
41
42 return {"MyObject.pt": corrected_pt, "MyObject.mass": corrected_mass}
Advanced Example with Dependencies#
For calibrators that depend on other calibrators’ output and/or on the original uncalibrated information.
1class AdvancedCalibrator(Calibrator):
2 name = "advanced_calibrator"
3 has_variations = True
4 isMC_only = True
5 calibrated_collections = ["DerivedQuantity"]
6
7 def calibrate(
8 self, events, orig_colls, variation, already_applied_calibrators=None
9 ):
10 # Check dependencies
11 if "jet_calibration" not in already_applied_calibrators:
12 raise ValueError(
13 "This calibrator requires jet_calibration to be applied first"
14 )
15
16 # Use original jets if needed for some calculation
17 if "Jet" in orig_colls:
18 original_jets = orig_colls["Jet"]
19
20 # Use calibrated jets from events
21 # Reading from "events" in practice is taking all the objects calibrated up to this point in the sequence.
22 calibrated_jets = events["Jet"]
23
24 # Compute derived quantity
25 derived = self.compute_derived_quantity(
26 original_jets, calibrated_jets, variation
27 )
28
29 return {"DerivedQuantity": derived}
Systematic Variations#
Variation Naming Convention#
Systematic variations should follow the pattern: "{source}Up" / "{source}Down" where source describes the uncertainty source (e.g., “AK4PFchs_jec”, “AK4PFchs_jer”, “ele_scale”).
Examples:
"AK4PFchs_jecUp","AK4PFchs_jerDown"(fromJetsCalibrator)"ele_scaleUp","ele_scaleDown"(fromElectronsScaleCalibrator)
Configuration in Analysis#
Variations are configured in the variations.shape section:
1variations = {
2 "shape": {
3 "common": {
4 "inclusive": [
5 "jet_calibration", # All JEC/JER variations
6 "electron_scale_and_smearing", # All electron variations
7 ],
8 },
9 "bysample": {
10 "TTbar": {
11 "inclusive": ["custom_calibrator"], # Sample-specific variations
12 }
13 },
14 }
15}
Automatic Propagation#
The framework automatically:
Collects all variations from configured calibrators
Loops over each variation during processing
Fills separate histograms for each variation
Resets events to original state between variations
Integration with Workflow#
The calibration system is seamlessly integrated into the base workflow:
Initialization#
1def initialize_calibrators(self):
2 self.calibrators_manager = CalibratorsManager(
3 self.cfg.calibrators,
4 self.events,
5 self.params,
6 self._metadata,
7 requested_calibrator_variations=self.cfg.available_shape_variations[self._sample],
8 )
Variation Loop#
1def loop_over_variations(self):
2 for variation, events_calibrated in self.calibrators_manager.calibration_loop(
3 self.events,
4 variations_for_calibrators=self.cfg.available_shape_variations[self._sample],
5 ):
6 self.events = events_calibrated
7 yield variation
Best Practices#
Performance#
Heavy computations should be done in
initialize()once per chunkLight corrections can be applied dynamically in
calibrate()Cache expensive operations when possible
Dependencies#
Declare dependencies explicitly by checking
already_applied_calibratorsUse original collections from
orig_collswhen neededOrder calibrators carefully in your sequence
Error Handling#
Validate inputs in both
initialize()andcalibrate()Check collection existence before accessing
Provide meaningful error messages
Testing#
Test with both MC and Data if applicable
Verify all declared collections are actually modified
Check variation names follow conventions
Validate with different parameter configurations
Common Issues#
Collection not found: Check
calibrated_collectionsdeclarationVariation not applied: Verify variation name and configuration
Performance issues: Move heavy computation to
initialize()Dependency errors: Check calibrator order and requirements