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
12#include <CxxUtils/crc64.h>
13
14#ifdef XAOD_STANDALONE
17#endif
19
20#include <nanobind/nanobind.h>
21#include <nanobind/ndarray.h>
22#include <nanobind/operators.h>
23#include <nanobind/stl/list.h>
24#include <nanobind/stl/string.h>
25#include <nanobind/stl/vector.h>
26#include <nanobind/stl/pair.h>
27#include <nanobind/stl/map.h>
28
29#include <cstdio>
30#include <string>
31
32namespace nb = nanobind;
33
34using namespace nb::literals;
35
36// this function lets us take an object and translate the pointer to a hexadecimal representation
37// 140164452316520 becomes "0x7f7a9463f568"
39 char buffer[32]; // Ensure the buffer is large enough for the hex representation
40 std::snprintf(buffer, sizeof(buffer), "0x%llx", reinterpret_cast<unsigned long long>(&obj));
41 return std::string(buffer);
42}
43
44// this function converts us from type_info to a human-readable name
45std::string get_type_name(const std::type_info& type_info) {
46 if (type_info == typeid(int)) {
47 return "int32";
48 } else if (type_info == typeid(unsigned int)) {
49 return "uint32";
50 } else if (type_info == typeid(short)) {
51 return "int16";
52 } else if (type_info == typeid(unsigned short)) {
53 return "uint16";
54 } else if (type_info == typeid(char)) {
56 return "int8";
57 } else if (type_info == typeid(unsigned char)) {
58 return "uint8";
59 } else if (type_info == typeid(float)) {
60 return "float32";
61 } else if (type_info == typeid(double)) {
62 return "float64";
63 } else if (type_info == typeid(long)) {
64 return "int64";
65 } else if (type_info == typeid(unsigned long)) {
66 return "uint64";
67 } else if (type_info == typeid(bool)) {
68 return "bool";
69 } else {
70 // If the type is unknown, you can return the mangled name or a default message
71 return std::string("unknown ('") + type_info.name() + "')";
72 }
73}
74
75void setProperty(columnar::PythonToolHandle &self, const std::string& key, nb::object value){
76 if (nb::isinstance<nb::str>(value)) {
77 self.setProperty(key, nb::cast<std::string>(value));
78 } else if (nb::isinstance<nb::int_>(value)) {
79 self.setProperty(key, nb::cast<int>(value));
80 } else if (nb::isinstance<nb::float_>(value)) {
81 self.setProperty(key, nb::cast<double>(value));
82 } else {
83 throw std::runtime_error("Unsupported property type. Must be str, int, or float.");
84 }
85}
86
87nb::object getColumnVoid(columnar::PythonToolHandle &self, const std::string& key) {
88 auto [size, ptr, type] = self.getColumnVoid(key);
89 // Wrap the raw pointer in a numpy array without copying. The caller is
90 // responsible for keeping the PythonToolHandle alive while using the result.
91#define MAKE_NDARRAY(T) \
92 nb::ndarray<nb::numpy, T, nb::ro>(static_cast<const T*>(ptr), {size}).cast()
93 if (*type == typeid(float)) return MAKE_NDARRAY(float);
94 if (*type == typeid(double)) return MAKE_NDARRAY(double);
95 if (*type == typeid(char)) return MAKE_NDARRAY(char);
96 if (*type == typeid(int)) return MAKE_NDARRAY(int);
97 if (*type == typeid(std::uint8_t)) return MAKE_NDARRAY(std::uint8_t);
98 if (*type == typeid(std::uint16_t)) return MAKE_NDARRAY(std::uint16_t);
99 if (*type == typeid(std::uint32_t)) return MAKE_NDARRAY(std::uint32_t);
100 if (*type == typeid(std::uint64_t)) return MAKE_NDARRAY(std::uint64_t);
101 if (*type == typeid(std::int16_t)) return MAKE_NDARRAY(std::int16_t);
102 if (*type == typeid(std::int32_t)) return MAKE_NDARRAY(std::int32_t);
103 if (*type == typeid(std::int64_t)) return MAKE_NDARRAY(std::int64_t);
104#undef MAKE_NDARRAY
105 throw std::runtime_error("getColumnVoid: unsupported column type: " + std::string(type->name()));
106}
107
108void setColumnVoid(columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<> column, bool is_const = true) {
109 // TODO: figure out how to get type_info from handle instead...
110 // nb::handle handle = column.handle();
111
112 const std::type_info* type_info = nullptr;
113 const nb::dlpack::dtype dtype = column.dtype();
114 switch ((nb::dlpack::dtype_code) dtype.code) {
115 case nb::dlpack::dtype_code::Int:
116 switch (dtype.bits) {
117 // escape hatch to handle char for now
118 // we should rely on signed/unsigned and nbits, instead of std::type_info
119 // case 8: type_info = &typeid(std::int8_t); break;
120 case 8: type_info = &typeid(char); break;
121 case 16: type_info = &typeid(std::int16_t); break;
122 case 32: type_info = &typeid(std::int32_t); break;
123 case 64: type_info = &typeid(std::int64_t); break;
124 }
125 break;
126
127 case nb::dlpack::dtype_code::UInt:
128 switch (dtype.bits) {
129 case 8: type_info = &typeid(std::uint8_t); break;
130 case 16: type_info = &typeid(std::uint16_t); break;
131 case 32: type_info = &typeid(std::uint32_t); break;
132 case 64: type_info = &typeid(std::uint64_t); break;
133 }
134 break;
135
136 case nb::dlpack::dtype_code::Float:
137 switch (dtype.bits) {
138 case 32: type_info = &typeid(float); break;
139 case 64: type_info = &typeid(double); break;
140 }
141 break;
142
143 default:
144 break;
145 }
146
147 if (type_info == nullptr) throw std::runtime_error ("unsupported column type passed in");
148 self.setColumnVoid(key, column.shape(0), column.data(), *type_info, is_const);
149}
150
151void setImmutableColumnVoid(columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<> column){
152 setColumnVoid(self, key, column, true);
153};
154
155namespace {
156
157#ifdef XAOD_STANDALONE
160struct PyMessagePrinter : public asg::IMessagePrinter {
161 nb::callable m_callback;
162
163 explicit PyMessagePrinter(nb::callable cb)
164 : m_callback(std::move(cb)) {}
165
166 void print(MSG::Level lvl, const std::string& name,
167 const std::string& text) override {
168 // Guard against Python interpreter shutdown
169 if (!Py_IsInitialized())
170 return;
171 nb::gil_scoped_acquire gil;
172 m_callback(static_cast<int>(lvl), name, text);
173 }
174};
175
176// Global state for printer management
177static std::unique_ptr<PyMessagePrinter> g_printer;
178static std::unique_ptr<asg::MessagePrinterOverlay> g_overlay;
179
180void set_printer_from_callable(nb::callable cb) {
181 g_printer = std::make_unique<PyMessagePrinter>(std::move(cb));
182 g_overlay.reset();
183 g_overlay = std::make_unique<asg::MessagePrinterOverlay>(g_printer.get());
184}
185
186void clear_printer() {
187 g_overlay.reset();
188 g_printer.reset();
189}
190#endif // XAOD_STANDALONE
191
192} // anonymous namespace
193
194
195NB_MODULE(python_tool_handle, module) {
196 module.doc() = "Nanobind bindings for PythonToolHandle";
197
199 throw nb::import_error("This module can only be used in columnar access mode. Try setting up a ColumnarAnalysis release instead.");
200
201 module.attr("numberOfEventsName") = &columnar::eventRangeColumnName;
202 module.attr("eventRangeColumnName") = &columnar::eventRangeColumnName;
203
204 // the value used for an invalid (null/empty) element link in array mode
205 module.attr("invalid_link_value") = columnar::ColumnarModeArray::invalidLinkValue;
206
207 module.def("crc64",
208 [](const std::string& data) { return CxxUtils::crc64(data); },
209 "data"_a,
210 "CRC-64 of a string, using the Athena default polynomial "
211 "(CxxUtils::crc64).");
212
213 module.def("crc64addint", &CxxUtils::crc64addint,
214 "crc"_a, "x"_a,
215 "Extend a previously-calculated CRC-64 to include an integer "
216 "(CxxUtils::crc64addint).");
217
218 module.def("sg_key",
219 &columnar::computeSgKey,
220 "name"_a, "clid"_a = 0,
221 "StoreGate hash (sgkey) of a container name, optionally mixed with "
222 "its CLID. ElementLink m_persKey values in Athena-written files are "
223 "sg_key(container_name, container_clid).");
224
225 // Install a Python callable as the global C++ message printer.
226#ifdef XAOD_STANDALONE
227 // Overload 1: with callback function
228 module.def("set_python_printer", &set_printer_from_callable, nb::arg("callback"),
229 "Install a Python callable(level: int, name: str, text: str) as the global "
230 "C++ message printer.");
231
232 // Overload 2: reset (no argument)
233 module.def("set_python_printer", &clear_printer,
234 "Reset to the default stdout message printer.");
235#else
236 // In Athena/AthAnalysis builds IMessagePrinter does not exist; expose the
237 // function so Python code doesn't get AttributeError, but raise at call time.
238 module.def("set_python_printer", [](nb::args, nb::kwargs) {
239 throw std::runtime_error(
240 "set_python_printer is only available in standalone "
241 "(AnalysisBase/ColumnarAnalysis) builds, not in Athena/AthAnalysis.");
242 }, "Not available in Athena/AthAnalysis builds.");
243#endif // XAOD_STANDALONE
244
246 nb::enum_<columnar::ColumnAccessMode>(module, "ColumnAccessMode")
247 .value("input", columnar::ColumnAccessMode::input)
248 .value("output", columnar::ColumnAccessMode::output)
249 .value("update", columnar::ColumnAccessMode::update)
250 .def("__repr__", [](const columnar::ColumnAccessMode &mode) -> std::string {
251 switch (mode) {
253 return "<ColumnAccessMode input>";
255 return "<ColumnAccessMode output>";
257 return "<ColumnAccessMode update>";
258 default:
259 return "<ColumnAccessMode update value=" + std::to_string(static_cast<int>(mode)) + ">";
260 }
261 })
262 .export_values(); // Makes the enum values accessible without namespace in Python
263
264 nb::enum_<MSG::Level>(module, "MsgLevel", nb::is_arithmetic())
265 .value("NIL", MSG::NIL)
266 .value("VERBOSE", MSG::VERBOSE)
267 .value("DEBUG", MSG::DEBUG)
268 .value("INFO", MSG::INFO)
269 .value("WARNING", MSG::WARNING)
270 .value("ERROR", MSG::ERROR)
271 .value("FATAL", MSG::FATAL)
272 .export_values();
273
274 nb::class_<columnar::ColumnInfo>(module, "ColumnInfo")
275 .def(nb::init<>()) // Default constructor
276 .def_ro("name", &columnar::ColumnInfo::name)
277 .def_ro("index", &columnar::ColumnInfo::index)
278 .def_prop_ro("dtype", [](const columnar::ColumnInfo &self){
279 return get_type_name(*self.type);
280 })
281 .def_ro("access_mode", &columnar::ColumnInfo::accessMode)
282 .def_ro("offset_name", &columnar::ColumnInfo::offsetName)
283 .def_ro("fixed_dimensions", &columnar::ColumnInfo::fixedDimensions)
284 .def_ro("sole_link_target_name", &columnar::ColumnInfo::soleLinkTargetName)
285 .def_ro("sole_link_target_clid", &columnar::ColumnInfo::soleLinkTargetClid)
286 .def_ro("is_variant_link", &columnar::ColumnInfo::isVariantLink)
287 .def_ro("variant_link_target_names", &columnar::ColumnInfo::variantLinkTargetNames)
288 .def_ro("key_column_for_variant_link", &columnar::ColumnInfo::keyColumnForVariantLink)
289 .def_ro("is_offset", &columnar::ColumnInfo::isOffset)
290 .def_ro("replaces_column", &columnar::ColumnInfo::replacesColumn)
291 .def_ro("is_optional", &columnar::ColumnInfo::isOptional)
292 .def("__repr__", [](const columnar::ColumnInfo &self) {
293 std::string access_mode;
294 switch (self.accessMode) {
296 access_mode = "input";
297 break;
299 access_mode = "output";
300 break;
302 access_mode = "update";
303 break;
304 default:
305 // For unknown values, return the integer value
306 access_mode = "unknown";
307 }
308 return "<ColumnInfo name='" + self.name + "'" +
309 (self.isOffset ? "" : ", offset='" + self.offsetName + "'") +
310 ", access_mode='" + access_mode + "'" +
311 ", dtype='" + (self.type ? get_type_name(*self.type) : "" ) + "'" +
312 (self.isOptional ? ", optional": "") +
313 ">";
314 })
315 .def("to_dict", [](const columnar::ColumnInfo& self) {
316 nb::dict d;
317 d["name"] = self.name;
318 d["index"] = self.index;
319 d["dtype"] = self.type ? get_type_name(*self.type) : "";
320 d["access_mode"] = static_cast<int>(self.accessMode);
321 d["offset_name"] = self.offsetName;
322 d["fixed_dimensions"] = self.fixedDimensions;
323 d["sole_link_target_name"] = self.soleLinkTargetName;
324 d["sole_link_target_clid"] = self.soleLinkTargetClid;
325 d["is_variant_link"] = self.isVariantLink;
326 d["variant_link_target_names"] = self.variantLinkTargetNames;
327 d["key_column_for_variant_link"] = self.keyColumnForVariantLink;
328 d["is_offset"] = self.isOffset;
329 d["replaces_column"] = self.replacesColumn;
330 d["is_optional"] = self.isOptional;
331 return d;
332 });
333
334 nb::class_<columnar::PythonToolHandle>(module, "PythonToolHandle")
335 .def(nb::init())
336
337 // Properties
338 .def_prop_ro("type", [](const columnar::PythonToolHandle &self) {
339 const asg::AsgToolConfig& config = self.getConfig();
340 const std::string& type = config.type();
341 if (type.empty()) {
342 std::cerr << "Warning: PythonToolHandle.type is empty."
343 << " Set with PythonToolHandle.set_type_and_name." << std::endl;
344 }
345 return type;
346 })
347
348 .def_prop_ro("name", [](const columnar::PythonToolHandle &self) {
349 const asg::AsgToolConfig& config = self.getConfig();
350 const std::string& name = config.name();
351 if (name.empty()) {
352 std::cerr << "Warning: PythonToolHandle.name is empty."
353 << " Set with PythonToolHandle.set_type_and_name." << std::endl;
354 }
355 return name;
356 })
357
358 // Methods
359 .def("set_type_and_name",
360 [](columnar::PythonToolHandle &self, const std::string& type_and_name) {
361 self.setTypeAndName(type_and_name);
362 },
363 "type_and_name"_a,
364 "Set the type and name of the tool.")
365
366 .def("set_property", &setProperty,
367 "key"_a, "value"_a,
368 "Set a property on the tool.")
369
370 .def("__setattr__", &setProperty,
371 "key"_a, "value"_a,
372 "Set a property on the tool.")
373
374 .def("preinitialize",
376 "Preinitialize the tool.")
377
378 // rename_containers([("from", "to"), ...])
379 .def("rename_containers",
381 "renames"_a,
382 "Rename the columns the tool uses.")
383
384 // rename_containers({"from": "to"}, ...})
385 .def("rename_containers",
386 [](columnar::PythonToolHandle &self, const std::map<std::string,std::string>& renames){
387 std::vector<std::pair<std::string, std::string>> vectorized;
388 for (const auto& pair : renames)
389 vectorized.emplace_back(pair);
390
391 return self.renameContainers(vectorized);
392 },
393 "renames"_a,
394 "Rename the columns the tool uses.")
395
396 .def("initialize",
398 "Initialize the tool.")
399
400 .def("apply_systematic_variation",
401 [](columnar::PythonToolHandle &self, const std::string& sys_name) {
402 self.applySystematicVariation(sys_name);
403 },
404 "sys_name"_a,
405 "Apply a systematic variation to the tool.")
406
407 .def("set_column",
408 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<float> column) {
409 self.setColumn<float>(key, column.shape(0), column.data());
410 },
411 "key"_a, "column"_a,
412 "Set a float column pointer.")
413
414 .def("set_column",
415 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<char> column) {
416 self.setColumn<char>(key, column.shape(0), column.data());
417 },
418 "key"_a, "column"_a,
419 "Set a char column pointer.")
420
421 .def("set_column",
422 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<int> column) {
423 self.setColumn<int>(key, column.shape(0), column.data());
424 },
425 "key"_a, "column"_a,
426 "Set an int column pointer.")
427
428 .def("set_column",
429 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<std::uint8_t> column) {
430 self.setColumn<uint8_t>(key, column.shape(0), column.data());
431 },
432 "key"_a, "column"_a,
433 "Set a uint8_t column pointer.")
434
435 .def("set_column",
436 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<std::uint16_t> column) {
437 self.setColumn<uint16_t>(key, column.shape(0), column.data());
438 },
439 "key"_a, "column"_a,
440 "Set a uint16_t column pointer.")
441
442 .def("set_column",
443 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<std::uint32_t> column) {
444 self.setColumn<uint32_t>(key, column.shape(0), column.data());
445 },
446 "key"_a, "column"_a,
447 "Set a uint32_t column pointer.")
448
449 .def("set_column",
450 [](columnar::PythonToolHandle &self, const std::string& key, nb::ndarray<std::uint64_t> column) {
451 self.setColumn<uint64_t>(key, column.shape(0), column.data());
452 },
453 "key"_a, "column"_a,
454 "Set a uint64_t column pointer.")
455
456 .def("set_column_void", &setColumnVoid,
457 // cppcheck-suppress assignBoolToPointer
458 "key"_a, "column"_a, "is_const"_a = true,
459 "Set a void column pointer (nanobind version).")
460
461 .def("__setitem__", &setImmutableColumnVoid,
462 "key"_a, "column"_a,
463 "Set a void immutable column pointer (nanobind version).")
464
465 .def("__getitem__", &getColumnVoid,
466 "key"_a,
467 "Get a column as a numpy array (zero-copy view into the tool's buffer).")
468
469 .def("keys",
471 "Return the column names (enables dict(handle)).")
472
473 .def("call",
475 "Call the tool and reset the columns.")
476
477 .def_prop_ro(
478 "columns",
480 "Get the expected column information."
481 )
482
483 .def("get_recommended_systematics",
485 "Get the recommended systematics.")
486
487 // Make this more fancy in the future
488 // <PythonToolHandle(CP::MuonEfficiencyScaleFactors/unique0) object at 0x7f2943b07568>
489 .def("__repr__", [](const columnar::PythonToolHandle &self) {
490 const asg::AsgToolConfig& config = self.getConfig();
491 return "<PythonToolHandle(" + config.type() + "/" + config.name() + ") object at " + getAddressString(self) + ">";
492 });
493}
Definition of message levels and a helper function.
NB_MODULE(python_tool_handle, module)
nb::object getColumnVoid(columnar::PythonToolHandle &self, const std::string &key)
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)
#define MAKE_NDARRAY(T)
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)
size_t size() const
Number of registered mappings.
void print(char *figname, TCanvas *c1)
an object that can create a AsgTool
a handle to a python tool for use via nanobind
void setColumn(const std::string &key, std::size_t size, CT *dataPtr)
set a column pointer (raw pointer version)
void renameContainers(const std::vector< std::pair< std::string, std::string > > &renames)
rename the columns the tool uses
void preinitialize()
preinitialize the tool
void applySystematicVariation(const std::string &sysName)
set the tool to apply the given systematic variation
void setTypeAndName(const std::string &typeAndName)
set the type and name for 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::tuple< std::size_t, const void *, const std::type_info * > getColumnVoid(const std::string &name) const
get a column pointer by name; returns {size, ptr, type_info}
const asg::AsgToolConfig & getConfig() const
get the AsgToolConfig
std::vector< ColumnInfo > getColumnInfo() const
get the expected column info
std::vector< std::string > getColumnNames() const
get the expected column names
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.
A crc-64 implementation, using pclmul where possible.
constexpr unsigned columnarAccessMode
ColumnAccessMode
an enum for the different access modes for a column
Definition ColumnInfo.h:20
@ update
an updateable column
Definition ColumnInfo.h:28
@ output
an output column
Definition ColumnInfo.h:25
@ input
an input column
Definition ColumnInfo.h:22
setWord1 uint16_t
setEventNumber uint32_t
a struct that contains meta-information about each column that's needed to interface the column with ...
Definition ColumnInfo.h:36
std::string offsetName
the name of the offset column used for this column (or empty string for none)
Definition ColumnInfo.h:75
std::string soleLinkTargetName
for simple link columns: the name of the target container
Definition ColumnInfo.h:132
std::string keyColumnForVariantLink
if this is a key column for a variant link, the name of the associated link column
Definition ColumnInfo.h:192
std::uint32_t soleLinkTargetClid
for simple link columns: the CLID of the target container
Definition ColumnInfo.h:146
std::vector< unsigned > fixedDimensions
the fixed dimensions this column has (if any)
Definition ColumnInfo.h:83
bool isOptional
whether this column is optional
Definition ColumnInfo.h:122
std::vector< std::string > variantLinkTargetNames
for variant link key columns: the names of the containers we can link to
Definition ColumnInfo.h:177
std::string name
the name of the column
Definition ColumnInfo.h:43
bool isOffset
whether this is an offset column
Definition ColumnInfo.h:93
ColumnAccessMode accessMode
the access mode for the column
Definition ColumnInfo.h:59
std::string replacesColumn
whether this replaces another column
Definition ColumnInfo.h:103
bool isVariantLink
whether this is a variant link column
Definition ColumnInfo.h:154
unsigned index
the index of the column in the data array
Definition ColumnInfo.h:47
const std::type_info * type
the type of the individual entries in the column
Definition ColumnInfo.h:55