coffea

repository·master·Indexed 40 days ago

https://github.com/scikit-hep/coffea

A columnar object framework for efficient high-energy collider physics (HEP) analysis. It provides tools for manipulating HEP event data using the scientific Python ecosystem, including the Weights class for systematic variations, PackedSelection for memory-efficient event selections, and lookup_tools for applying corrections from ROOT histograms. It also includes jetmet_tools for CMS jet energy corrections and a Pydantic-based FileSpec hierarchy for type-safe file and dataset management.

Tokens
32.7K
Snippets
94
Records
132
Agent score
49%

What's inside coffea

  1. Choose an executor for your workload

    master

    Select an executor based on your environment and scale requirements:

    • IterativeExecutor: Single-threaded execution in the current process. Best for debugging, unit tests, and tutorials.
    • FuturesExecutor: Parallelizes work across local CPU cores using concurrent.futures. Best for local multi-core scaling.
    • DaskExecutor: Connects to a Dask Distributed cluster. Best for interactive or large-scale batch workloads.
    • ParslExecutor: Targets HPC facilities via Parsl.
    • TaskVineExecutor: Dispatches work to resources managed by TaskVine.
  2. What is columnar analysis in Coffea

    master

    Columnar analysis is a paradigm where analysis operates on individual columns of data spanning a chunk (partition or batch) of rows using array programming primitives, rather than using a traditional event loop that operates row-wise.

    While standard scientific Python tools like numpy are designed for rectangular arrays, HEP data is often irregular. Coffea leverages the awkward package to extend array programming capabilities to handle the complex, irregular structures typical of HEP data.

  3. Tips for efficient correction application

    master

    When applying corrections in Coffea, consider these best practices:

    • Persist helper arrays: If multiple corrections require the same derived values (e.g., absolute eta), compute them once and attach them to the events object to avoid redundant computation.
    • Record versions: Include the versions of the correction files used in your output dictionary or metadata to ensure reproducibility.
    • Use Weights class: For managing complex combinations of multiple corrections and systematics, use the Weights class from coffea.analysis_tools (refer to the applying_corrections notebook for specific examples).
  4. The Coffea analysis workflow pattern

    master

    Coffea uses a columnar data model (via Awkward Array) paired with a thin execution layer. This allows analysis code to transition from a local laptop to a high-performance cluster without code rewrites. The standard workflow follows three steps:

    1. Implement a Processor: Create a class inheriting from coffea.processor.ProcessorABC that transforms input data (e.g., NanoEvents) into accumulators (like histograms).
    2. Iterate Locally: Execute the processor using coffea.processor.Runner with a local executor (like IterativeExecutor) to debug and refine the logic.
    3. Scale Out: Swap the local executor for a distributed one (like DaskExecutor) when ready to process large datasets on a cluster.
    import awkward as ak
    import correctionlib
    import hist
    from coffea import processor
    
    class MuonProcessor(processor.ProcessorABC):
        def __init__(self, sf_path: str):
            self.corrections = correctionlib.CorrectionSet.from_file(sf_path)
            self.muon_sf = self.corrections["muon_sf"]
    
        def process(self, events):
            # ... logic to transform events into accumulators ...
            return {"dataset": {"mass": h_mass, "events": len(events)}}
    
        def postprocess(self, accumulator):
            return accumulator
  5. When to use accumulator classes

    master

    Most common tasks do not require specialized accumulator classes:

    • Histograms: Use hist.Hist (supports merging).
    • Scalar sums: Return plain int or float (automatically summed).
    • Dictionaries: Return plain dict (nested values automatically merged).

    Use accumulator classes only when you need specialized merging behavior like concatenation, unions, or auto-initialization.

  6. How executors and Runner work together

    master

    Coffea separates analysis logic from execution strategy. The coffea.processor.Runner class consumes an executor to manage concurrency and chunk scheduling. Because all executors implement the same interface, you can write your processor logic once and switch between local and distributed execution without changing your analysis code.

    # The processor logic remains the same
    my_processor = MyProcessor()
    
    # You only change the Runner/Executor configuration to scale
    runner = processor.Runner(executor=processor.IterativeExecutor(), ...)
    result = runner(fileset, processor_instance=my_processor)
  7. Use Lorentz-vector behaviors with NanoAODSchema

    master

    When using NanoAODSchema, collections are associated with Lorentz-vector behaviors. This allows you to perform high-level physics operations like calculating invariant mass directly on the objects. For example, adding two Lorentz vectors (like lead + trail) automatically enables access to properties like .mass and .pt.

    import awkward as ak
    
    lead, trail = ak.unzip(ak.combinations(tight_muons, 2))
    dimuon = lead + trail
    
    mass = dimuon.mass        # automatically computed invariant mass
    pt = dimuon.pt
  8. Scale-out with Local and Distributed executors

    master

    Coffea provides multiple executors that implement a unified interface, allowing you to switch from local debugging to large-scale cluster execution by only changing the executor argument in the Runner.

    Local Executors

    Used for running on a single machine:

    • IterativeExecutor: Processes chunks sequentially in a single Python thread. Best for debugging and validation.
    • FuturesExecutor: Uses concurrent.futures to parallelize work across multiple local workers (typically a process pool). You can tune parallelism using the workers or pool arguments.

    Distributed Executors

    Used for connecting to clusters and grid-style resources:

    • DaskExecutor: Integrates with a Dask Distributed cluster via a distributed.Client.
    • ParslExecutor: Uses Parsl to target various HPC and batch backends.
    • TaskVineExecutor: Leverages TaskVine for opportunistic and heterogeneous workers.
  9. How the Coffea processor and Runner work together

    master

    Coffea uses a pattern where each chunk of rows is processed independently, allowing for easy horizontal scale-out.

    • coffea.processor.ProcessorABC: You implement your analysis by subclassing this class and defining a process method that returns an accumulator.
    • coffea.processor.Runner: This helper manages the orchestration. It handles dataset chunking, the creation of NanoEvents, and the reduction of per-chunk results into a final output.

    The Runner allows you to execute your processor using the same interface regardless of whether you are running locally or on a distributed cluster.

    from coffea import processor
    from coffea.nanoevents import NanoAODSchema
    
    # Assume ``my_processor`` is an instance of a subclass of ProcessorABC.
    fileset = {
        "ZJets": {"treename": "Events", "files": ["/data/nano_dy.root"]},
        "Data": {"treename": "Events", "files": ["/data/nano_dimuon.root"]},
    }
    
    runner = processor.Runner(
        executor=processor.FuturesExecutor(workers=4, status=True),
        schema=NanoAODSchema,
    )
    
    result = runner(
        fileset,
        processor_instance=my_processor,
        treename="Events",
    )
  10. Install Coffea for development

    master

    To contribute to Coffea, clone the repository and install it in editable mode with development dependencies.

    Requirements:

    • To use the --group flag with pip, you need pip version 25.1 or newer.
    • Alternatively, you can use uv for environment management.

    Steps:

    1. Clone the source.
    2. Install with the dev dependency group.
    3. (Optional) Install with dask support if working on executors.
    4. Run linting, tests, and documentation builds using pre-commit, pytest, and make.
    # 1. Download source
    git clone https://github.com/scikit-hep/coffea
    
    # 2. Install with development dependencies
    cd coffea
    pip install --editable . --group dev
    
    # Or if you need to work on the executors (e.g. dask)
    pip install --editable '.[dask]' --group dev
    
    # Or using uv
    uv sync
    
    # 4. Lint, test, and build docs
    pre-commit run --all-files
    pytest tests
    pushd docs && make html && popd
  11. Best practices for efficient NanoEvents processing

    master

    To maximize performance in Coffea:

    • Keep processing columnar: Avoid explicit Python loops over events or particles. Use vectorized operations to minimize interpreter overhead and support batching.
    • Use Numba for loops: If a loop is unavoidable, wrap the hot section in a function decorated with @numba.njit to compile it to machine code.
    • Filter early: Apply boolean masks (selections) before performing expensive operations like ak.combinations to avoid unnecessary pairings.
    • Check object counts: Use ak.num(collection, axis=1) to inspect how many objects exist per event.
  12. Apply event-level weights from metadata

    master

    For global factors that do not depend on per-object kinematics (like cross-section or luminosity), retrieve the values from the events.metadata object and multiply them into your event weight.

    xsec = events.metadata["cross_section"]
    luminosity = 35.9
    event_weight *= xsec * luminosity / events.metadata["n_events"]
    xsec = events.metadata["cross_section"]
    luminosity = 35.9
    event_weight *= xsec * luminosity / events.metadata["n_events"]