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, **kwargs)#
Called to initialize the Calibrator and store necessary metadata for easy later usage.
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:
JetsCalibrator#
Name:
"jet_calibration"Purpose: Applies Jet Energy Corrections (JEC) and Jet Energy Resolution (JER)
Collections:
["Jet", "FatJet"]Variations: JEC and JER uncertainties (e.g.,
"jet_jecUp","jet_jerDown")
METCalibrator#
Name:
"met_rescaling"Purpose: Propagates jet corrections to Missing Energy (MET)
Collections:
["MET.pt", "MET.phi"](configurable)Dependencies: Must run after JetsCalibrator
ElectronsScaleCalibrator#
Name:
"electron_scale_and_smearing"Purpose: Applies electron energy scale and resolution corrections
Collections:
["Electron.pt", "Electron.pt_original"]Variations:
"ele_scaleUp/Down","ele_smearUp/Down"(MC only)
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, **kwargs):
12 super().__init__(params, metadata, **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}_{direction}" where:
source: describes the uncertainty source (e.g., “jec”, “jer”, “ele_scale”)direction: either “Up” or “Down”
Examples:
"jet_jecUp","jet_jecDown""ele_scaleUp","ele_scaleDown"
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 jme_factory=self.jmefactory, # Additional arguments passed to calibrators
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