ATLAS Offline Software
Loading...
Searching...
No Matches
tool.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2#
3# @author Giordon Stark
4
5# High-level Tool wrapper around PythonToolHandle.
6# Automates buffer extraction, column setting, calling, and output
7# reconstruction using ColumnInfo metadata.
8
9import awkward as ak
10import numpy as np
11
12from ColumnarToolWrapperPython.buffers import (
13 allocate_outputs,
14 classify_columns,
15 extract_buffers,
16 reconstruct_output,
17 resolve_optional_columns,
18)
19from ColumnarToolWrapperPython.python_tool_handle import PythonToolHandle
20
21
22class Tool:
23 """High-level wrapper around PythonToolHandle.
24
25 Handles the boilerplate of classifying columns, extracting awkward-array
26 buffers, allocating outputs, setting columns on the handle, calling the
27 tool, and reconstructing the output awkward array.
28
29 Parameters
30 ----------
31 type_and_name:
32 Tool type and instance name, e.g. "CP::MuonEfficiencyScaleFactors/myTool".
33 properties:
34 Optional dict of tool properties to set before initialization.
35 rename_containers:
36 Optional mapping from canonical container names (e.g. "Muons") to the
37 branch-name prefix used in the input arrays (e.g. "AnalysisMuonsAuxDyn").
38 This is passed to PythonToolHandle.rename_containers *after* initialize
39 so that ColumnInfo.name values match input array fields.
40 """
41
42 def __init__(self, type_and_name, properties=None, rename_containers=None):
43 self._handle = PythonToolHandle()
44 self._handle.set_type_and_name(type_and_name)
45 self._properties = dict(properties) if properties else {}
46
47 for key, value in self._properties.items():
48 self._handle.set_property(key, value)
49
50 self._handle.initialize()
51
52 if rename_containers:
53 self._handle.rename_containers(rename_containers)
54
55 # Cache classified column topology once after initialization
56 self._classified = classify_columns(self._handle.columns)
57
58 @property
59 def properties(self):
60 """Dict of properties set on this tool at construction time."""
61 return self._properties
62
63 @property
64 def columns(self):
65 """All ColumnInfo objects reported by the tool."""
66 return self._handle.columns
67
68 @property
69 def input_columns(self):
70 """Input (non-offset) ColumnInfo objects."""
71 cols = []
72 for info in self._classified.values():
73 cols.extend(info["inputs"])
74 return cols
75
76 @property
77 def output_columns(self):
78 """Output ColumnInfo objects."""
79 cols = []
80 for info in self._classified.values():
81 cols.extend(info["outputs"])
82 return cols
83
84 @property
86 """List of recommended systematic variation names."""
87 return self._handle.get_recommended_systematics()
88
89 def apply_systematic_variation(self, sys_name):
90 """Apply a systematic variation by name.
91
92 Parameters
93 ----------
94 sys_name:
95 Systematic variation name, e.g. ``"MUON_EFF_RECO_SYS__1up"``.
96 Pass ``""`` to reset to the nominal.
97 """
99
100 def __call__(self, events, systematic=None):
101 """Run the tool on events and return output columns as an ak.Array.
102
103 Parameters
104 ----------
105 events:
106 An ak.Array with fields matching the tool's input column names
107 (after any rename_containers mapping).
108 systematic:
109 Optional systematic variation name (e.g. "MUON_EFF_RECO_SYS__1up").
110 If provided, applied before running the tool and reset to nominal
111 after execution.
112
113 Returns
114 -------
115 ak.Array
116 Record array with one field per output column, each a
117 variable-length list over the per-particle values.
118 """
119 if systematic is not None:
120 self.apply_systematic_variation(systematic)
121
122 try:
123 num_events = int(ak.num(events, axis=0))
124
125 # Resolve optional columns against the actual fields present
126 effective = resolve_optional_columns(self._classified, events)
127
128 # Extract flat buffers from the awkward array
129 buffer_dict = extract_buffers(events, effective)
130
131 # Allocate zero-filled output arrays (added into buffer_dict in-place)
132 allocate_outputs(effective, buffer_dict)
133
134 # Set all columns on the handle
135 for container_name, info in effective.items():
136 # Container offset (always immutable)
137 self._handle[container_name] = np.asarray(buffer_dict[container_name])
138
139 # Nested-vector offsets (immutable)
140 for nested_name in info["nested_offsets"]:
141 if nested_name in buffer_dict:
142 self._handle[nested_name] = np.asarray(buffer_dict[nested_name])
143
144 # Input data columns (immutable)
145 for col in info["inputs"]:
146 self._handle[col.name] = np.asarray(buffer_dict[col.name])
147
148 # Output data columns (mutable)
149 for col in info["outputs"]:
150 self._handle.set_column_void(col.name, buffer_dict[col.name], False)
151
152 self._handle.call()
153
154 return reconstruct_output(effective, buffer_dict, num_events)
155 finally:
156 # Reset to nominal if systematic was applied
157 if systematic is not None:
apply_systematic_variation(self, sys_name)
Definition tool.py:89
output_columns(self)
Definition tool.py:77
__init__(self, type_and_name, properties=None, rename_containers=None)
Definition tool.py:42
recommended_systematics(self)
Definition tool.py:85
input_columns(self)
Definition tool.py:69
properties(self)
Definition tool.py:59
columns(self)
Definition tool.py:64
__call__(self, events, systematic=None)
Definition tool.py:100
void initialize()