ATLAS Offline Software
Loading...
Searching...
No Matches
NanobindBindings.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
8
11
12#include <nanobind/nanobind.h>
13#include <nanobind/ndarray.h>
14#include <nanobind/operators.h>
15#include <nanobind/stl/list.h>
16#include <nanobind/stl/string.h>
17#include <nanobind/stl/vector.h>
18#include <nanobind/stl/pair.h>
19#include <nanobind/stl/map.h>
20
21#include <cstdio>
22#include <string>
23
24namespace nb = nanobind;
25
26using namespace nb::literals;
27
28// this function lets us take an object and translate the pointer to a hexadecimal representation
29// 140164452316520 becomes "0x7f7a9463f568"
31 char buffer[32]; // Ensure the buffer is large enough for the hex representation
32 std::snprintf(buffer, sizeof(buffer), "0x%llx", reinterpret_cast<unsigned long long>(&obj));
33 return std::string(buffer);
34}
35
36// this function converts us from type_info to a human-readable name
37std::string get_type_name(const std::type_info& type_info) {
38 if (type_info == typeid(int)) {
39 return "int32";
40 } else if (type_info == typeid(unsigned int)) {
41 return "uint32";
42 } else if (type_info == typeid(short)) {
43 return "int16";
44 } else if (type_info == typeid(unsigned short)) {
45 return "uint16";
46 } else if (type_info == typeid(char)) {
48 return "int8";
49 } else if (type_info == typeid(unsigned char)) {
50 return "uint8";
51 } else if (type_info == typeid(float)) {
52 return "float32";
53 } else if (type_info == typeid(double)) {
54 return "float64";
55 } else if (type_info == typeid(long)) {
56 return "int64";
57 } else if (type_info == typeid(unsigned long)) {
58 return "uint64";
59 } else if (type_info == typeid(bool)) {
60 return "bool";
61 } else {
62 // If the type is unknown, you can return the mangled name or a default message
63 return std::string("unknown ('") + type_info.name() + "')";
64 }
65}
66
67void setProperty(columnar::PythonToolHandle &self, const std::string& key, nb::object value){
68 if (nb::isinstance<nb::str>(value)) {
69 self.setProperty(key, nb::cast<std::string>(value));
70 } else if (nb::isinstance<nb::int_>(value)) {
71 self.setProperty(key, nb::cast<int>(value));
72 } else if (nb::isinstance<nb::float_>(value)) {
73 self.setProperty(key, nb::cast<double>(value));
74 } else {
75 throw std::runtime_error("Unsupported property type. Must be str, int, or float.");
76 }
77}
78
79void setColumnVoid(columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<> column, bool is_const = true) {
80 // TODO: figure out how to get type_info from handle instead...
81 // nb::handle handle = column.handle();
82
83 const std::type_info* type_info = nullptr;
84 const nb::dlpack::dtype dtype = column.dtype();
85 switch ((nb::dlpack::dtype_code) dtype.code) {
86 case nb::dlpack::dtype_code::Int:
87 switch (dtype.bits) {
88 // escape hatch to handle char for now
89 // we should rely on signed/unsigned and nbits, instead of std::type_info
90 // case 8: type_info = &typeid(std::int8_t); break;
91 case 8: type_info = &typeid(char); break;
92 case 16: type_info = &typeid(std::int16_t); break;
93 case 32: type_info = &typeid(std::int32_t); break;
94 case 64: type_info = &typeid(std::int64_t); break;
95 }
96 break;
97
98 case nb::dlpack::dtype_code::UInt:
99 switch (dtype.bits) {
100 case 8: type_info = &typeid(std::uint8_t); break;
101 case 16: type_info = &typeid(std::uint16_t); break;
102 case 32: type_info = &typeid(std::uint32_t); break;
103 case 64: type_info = &typeid(std::uint64_t); break;
104 }
105 break;
106
107 case nb::dlpack::dtype_code::Float:
108 switch (dtype.bits) {
109 case 32: type_info = &typeid(float); break;
110 case 64: type_info = &typeid(double); break;
111 }
112 break;
113
114 default:
115 break;
116 }
117
118 if (type_info == nullptr) throw std::runtime_error ("unsupported column type passed in");
119 self.setColumnVoid(key, column.shape(0), column.data(), *type_info, is_const);
120}
121
122void setImmutableColumnVoid(columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<> column){
123 setColumnVoid(self, key, column, true);
124};
125
126
127NB_MODULE(python_tool_handle, module) {
128 module.doc() = "Nanobind bindings for PythonToolHandle";
129
131 throw nb::import_error("This module can only be used in columnar access mode. Try setting up a ColumnarAnalysis release instead.");
132
133 module.attr("numberOfEventsName") = &columnar::numberOfEventsName;
134
136 nb::enum_<columnar::ColumnAccessMode>(module, "ColumnAccessMode")
137 .value("input", columnar::ColumnAccessMode::input)
138 .value("output", columnar::ColumnAccessMode::output)
139 .value("update", columnar::ColumnAccessMode::update)
140 .def("__repr__", [](const columnar::ColumnAccessMode &mode) -> std::string {
141 switch (mode) {
143 return "<ColumnAccessMode input>";
145 return "<ColumnAccessMode output>";
147 return "<ColumnAccessMode update>";
148 default:
149 return "<ColumnAccessMode update value=" + std::to_string(static_cast<int>(mode)) + ">";
150 }
151 })
152 .export_values(); // Makes the enum values accessible without namespace in Python
153
154 nb::class_<columnar::ColumnInfo>(module, "ColumnInfo")
155 .def(nb::init<>()) // Default constructor
156 .def_ro("name", &columnar::ColumnInfo::name)
157 .def_ro("index", &columnar::ColumnInfo::index)
158 .def_prop_ro("dtype", [](const columnar::ColumnInfo &self){
159 return get_type_name(*self.type);
160 })
161 .def_ro("access_mode", &columnar::ColumnInfo::accessMode)
162 .def_ro("offset_name", &columnar::ColumnInfo::offsetName)
163 .def_ro("fixed_dimensions", &columnar::ColumnInfo::fixedDimensions)
164 .def_ro("link_target_names", &columnar::ColumnInfo::linkTargetNames)
165 .def_ro("variant_link_key_column", &columnar::ColumnInfo::variantLinkKeyColumn)
166 .def_ro("is_offset", &columnar::ColumnInfo::isOffset)
167 .def_ro("replaces_column", &columnar::ColumnInfo::replacesColumn)
168 .def_ro("is_optional", &columnar::ColumnInfo::isOptional)
169 .def("__repr__", [](const columnar::ColumnInfo &self) {
170 std::string access_mode;
171 switch (self.accessMode) {
173 access_mode = "input";
174 break;
176 access_mode = "output";
177 break;
179 access_mode = "update";
180 break;
181 default:
182 // For unknown values, return the integer value
183 access_mode = "unknown";
184 }
185 return "<ColumnInfo name='" + self.name + "'" +
186 (self.isOffset ? "" : ", offset='" + self.offsetName + "'") +
187 ", access_mode='" + access_mode + "'" +
188 ", dtype='" + (self.type ? get_type_name(*self.type) : "" ) + "'" +
189 (self.isOptional ? ", optional": "") +
190 ">";
191 })
192 .def("to_dict", [](const columnar::ColumnInfo& self) {
193 nb::dict d;
194 d["name"] = self.name;
195 d["index"] = self.index;
196 d["dtype"] = self.type ? get_type_name(*self.type) : "";
197 d["access_mode"] = static_cast<int>(self.accessMode);
198 d["offset_name"] = self.offsetName;
199 d["fixed_dimensions"] = self.fixedDimensions;
200 d["link_target_names"] = self.linkTargetNames;
201 d["variant_link_key_column"] = self.variantLinkKeyColumn;
202 d["is_offset"] = self.isOffset;
203 d["replaces_column"] = self.replacesColumn;
204 d["is_optional"] = self.isOptional;
205 return d;
206 });
207
208 nb::class_<columnar::PythonToolHandle>(module, "PythonToolHandle")
209 .def(nb::init())
210
211 // Properties
212 .def_prop_ro("type", [](const columnar::PythonToolHandle &self) {
213 const asg::AsgToolConfig& config = self.getConfig();
214 const std::string& type = config.type();
215 if (type.empty()) {
216 std::cerr << "Warning: PythonToolHandle.type is empty."
217 << " Set with PythonToolHandle.set_type_and_name." << std::endl;
218 }
219 return type;
220 })
221
222 .def_prop_ro("name", [](const columnar::PythonToolHandle &self) {
223 const asg::AsgToolConfig& config = self.getConfig();
224 const std::string& name = config.name();
225 if (name.empty()) {
226 std::cerr << "Warning: PythonToolHandle.name is empty."
227 << " Set with PythonToolHandle.set_type_and_name." << std::endl;
228 }
229 return name;
230 })
231
232 // Methods
233 .def("set_type_and_name",
234 [](columnar::PythonToolHandle &self, const std::string& type_and_name) {
235 self.setTypeAndName(type_and_name);
236 },
237 "type_and_name"_a,
238 "Set the type and name of the tool.")
239
240 .def("set_property", &setProperty,
241 "key"_a, "value"_a,
242 "Set a property on the tool.")
243
244 .def("__setattr__", &setProperty,
245 "key"_a, "value"_a,
246 "Set a property on the tool.")
247
248 .def("preinitialize",
250 "Preinitialize the tool.")
251
252 // rename_containers([("from", "to"), ...])
253 .def("rename_containers",
255 "renames"_a,
256 "Rename the columns the tool uses.")
257
258 // rename_containers({"from": "to"}, ...})
259 .def("rename_containers",
260 [](columnar::PythonToolHandle &self, const std::map<std::string,std::string>& renames){
261 std::vector<std::pair<std::string, std::string>> vectorized;
262 for (const auto& pair : renames)
263 vectorized.emplace_back(pair);
264
265 return self.renameContainers(vectorized);
266 },
267 "renames"_a,
268 "Rename the columns the tool uses.")
269
270 .def("initialize",
272 "Initialize the tool.")
273
274 .def("apply_systematic_variation",
275 [](columnar::PythonToolHandle &self, const std::string& sys_name) {
276 self.applySystematicVariation(sys_name);
277 },
278 "sys_name"_a,
279 "Apply a systematic variation to the tool.")
280
281 .def("set_column",
282 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<float> column) {
283 self.setColumn<float>(key, column.shape(0), column.data());
284 },
285 "key"_a, "column"_a,
286 "Set a float column pointer.")
287
288 .def("set_column",
289 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<char> column) {
290 self.setColumn<char>(key, column.shape(0), column.data());
291 },
292 "key"_a, "column"_a,
293 "Set a char column pointer.")
294
295 .def("set_column",
296 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<int> column) {
297 self.setColumn<int>(key, column.shape(0), column.data());
298 },
299 "key"_a, "column"_a,
300 "Set an int column pointer.")
301
302 .def("set_column",
303 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<std::uint8_t> column) {
304 self.setColumn<uint8_t>(key, column.shape(0), column.data());
305 },
306 "key"_a, "column"_a,
307 "Set a uint8_t column pointer.")
308
309 .def("set_column",
310 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<std::uint16_t> column) {
311 self.setColumn<uint16_t>(key, column.shape(0), column.data());
312 },
313 "key"_a, "column"_a,
314 "Set a uint16_t column pointer.")
315
316 .def("set_column",
317 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<std::uint32_t> column) {
318 self.setColumn<uint32_t>(key, column.shape(0), column.data());
319 },
320 "key"_a, "column"_a,
321 "Set a uint32_t column pointer.")
322
323 .def("set_column",
324 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<std::uint64_t> column) {
325 self.setColumn<uint64_t>(key, column.shape(0), column.data());
326 },
327 "key"_a, "column"_a,
328 "Set a uint64_t column pointer.")
329
330 .def("set_column_void", &setColumnVoid,
331 // cppcheck-suppress assignBoolToPointer
332 "key"_a, "column"_a, "is_const"_a = true,
333 "Set a void column pointer (nanobind version).")
334
335 .def("__setitem__", &setImmutableColumnVoid,
336 "key"_a, "column"_a,
337 "Set a void immutable column pointer (nanobind version).")
338
339 .def("call",
341 "Call the tool and reset the columns.")
342
343 .def_prop_ro(
344 "columns",
346 "Get the expected column information."
347 )
348
349 .def("get_recommended_systematics",
351 "Get the recommended systematics.")
352
353 // Make this more fancy in the future
354 // <PythonToolHandle(CP::MuonEfficiencyScaleFactors/unique0) object at 0x7f2943b07568>
355 .def("__repr__", [](const columnar::PythonToolHandle &self) {
356 const asg::AsgToolConfig& config = self.getConfig();
357 return "<PythonToolHandle(" + config.type() + "/" + config.name() + ") object at " + getAddressString(self) + ">";
358 });
359}
NB_MODULE(python_tool_handle, module)
std::string get_type_name(const std::type_info &type_info)
void setColumnVoid(columnar::PythonToolHandle &self, const std::string &key, nb::ndarray<> column, bool is_const=true)
void setProperty(columnar::PythonToolHandle &self, const std::string &key, nb::object value)
void setImmutableColumnVoid(columnar::PythonToolHandle &self, const std::string &key, nb::ndarray<> column)
std::string getAddressString(const columnar::PythonToolHandle &obj)
an object that can create a AsgTool
a handle to a python tool for use via nanobind
void renameContainers(const std::vector< std::pair< std::string, std::string > > &renames)
rename the columns the tool uses
void preinitialize()
preinitialize the tool
void setColumnVoid(const std::string &name, std::size_t size, const void *dataPtr, const std::type_info &type, bool isConst)
set a column pointer
std::vector< std::string > getRecommendedSystematics() const
get the recommended systematics
std::vector< ColumnInfo > getColumnInfo() const
get the expected column info
void setProperty(const std::string &key, T &&value)
set a property on the tool
void call()
call the tool and reset the columns
void initialize()
initialize the tool
STL class.
constexpr unsigned columnarAccessMode
Definition ColumnarDef.h:15
ColumnAccessMode
an enum for the different access modes for a column
Definition ColumnInfo.h:19
@ update
an updateable column
Definition ColumnInfo.h:27
@ output
an output column
Definition ColumnInfo.h:24
@ input
an input column
Definition ColumnInfo.h:21
a struct that contains meta-information about each column that's needed to interface the column with ...
Definition ColumnInfo.h:35
std::string offsetName
the name of the offset column used for this column (or empty string for none)
Definition ColumnInfo.h:74
std::vector< unsigned > fixedDimensions
the fixed dimensions this column has (if any)
Definition ColumnInfo.h:82
bool isOptional
whether this column is optional
Definition ColumnInfo.h:121
std::string variantLinkKeyColumn
if this is a variant link column, this is the name of the column with the container keys
Definition ColumnInfo.h:169
std::string name
the name of the column
Definition ColumnInfo.h:42
std::vector< std::string > linkTargetNames
for link columns: the name(s) of the container(s) we link to
Definition ColumnInfo.h:154
bool isOffset
whether this is an offset column
Definition ColumnInfo.h:92
ColumnAccessMode accessMode
the access mode for the column
Definition ColumnInfo.h:58
std::string replacesColumn
whether this replaces another column
Definition ColumnInfo.h:102
unsigned index
the index of the column in the data array
Definition ColumnInfo.h:46
const std::type_info * type
the type of the individual entries in the column
Definition ColumnInfo.h:54