cepgen is hosted by Hepforge, IPPP Durham
CepGen 1.2.5
Central exclusive processes event generator
Loading...
Searching...
No Matches
ObjectPtr.cpp
Go to the documentation of this file.
1/*
2 * CepGen: a central exclusive processes event generator
3 * Copyright (C) 2018-2024 Laurent Forthomme
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
20#include "CepGen/Utils/Limits.h"
22#include "CepGen/Utils/String.h"
25
26using namespace std::string_literals;
27
28namespace cepgen {
29 namespace python {
30 void obj_deleter(PyObject* obj) {
31 CG_DEBUG("python:ObjectPtrDeleter").log([&obj](auto& log) {
32 log << "Destroying object at addr 0x" << obj << " (";
33#if PY_VERSION_HEX >= 0x03110000
34 if (auto* type = Py_TYPE(obj); type)
35 log << "type: " << value<std::string>(PyType_GetName(type)) << ", ";
36#endif
37 log << "reference count: " << Py_REFCNT(obj) << ")";
38 });
39 Py_DECREF(obj);
40 }
41
42 ObjectPtr::ObjectPtr(PyObject* obj, bool wrap_only)
43 : PyObjectPtr(obj, wrap_only ? [](PyObject*) { /* do not dereference if only wrapping */ } : obj_deleter) {}
44
45 ObjectPtr ObjectPtr::wrap(PyObject* obj) {
46 ObjectPtr ptr(obj, true);
47 return ptr;
48 }
49
50 template <>
51 bool ObjectPtr::is<int>() const {
52 CG_ASSERT(get());
53#ifdef PYTHON2
54 return PyInt_Check(get());
55#else
56 return PyLong_Check(get());
57#endif
58 }
59
60 template <>
61 bool ObjectPtr::is<bool>() const {
62 CG_ASSERT(get());
63 return PyBool_Check(get());
64 }
65
66 template <>
67 bool ObjectPtr::is<long>() const {
68 CG_ASSERT(get());
69#ifdef PYTHON2
70 return PyInt_Check(get()) || PyLong_Check(get());
71#else
72 return PyLong_Check(get());
73#endif
74 }
75
76 template <>
77 bool ObjectPtr::is<double>() const {
78 CG_ASSERT(get());
79 return PyFloat_Check(get());
80 }
81
82 template <>
83 bool ObjectPtr::is<std::string>() const {
84 CG_ASSERT(get());
85#ifdef PYTHON2
86 return PyString_Check(get());
87#else
88 return PyUnicode_Check(get()) || PyBytes_Check(get());
89#endif
90 }
91
92 template <>
93 bool ObjectPtr::is<Limits>() const {
94 if (!isVector<double>())
95 return false;
96 if (const auto size = vector<double>().size(); size == 1 || size == 2)
97 return true;
98 return false;
99 }
100
101 template <>
102 bool ObjectPtr::is<ParametersList>() const {
103 CG_ASSERT(get());
104 return PyDict_Check(get());
105 }
106
107 template <typename T>
108 bool ObjectPtr::isVector() const {
109 CG_ASSERT(get());
110 const bool tuple = PyTuple_Check(get()), list = PyList_Check(get());
111 if (!tuple && !list) // only accept 'tuples' and 'lists'
112 return false;
113 if (const auto size = tuple ? PyTuple_Size(get()) : list ? PyList_Size(get()) : 0; size == 0)
114 return true;
115 const auto first = ObjectPtr::wrap(tuple ? PyTuple_GetItem(get(), 0) /* borrowed */
116 : list ? PyList_GetItem(get(), 0) /* borrowed */
117 : nullptr);
118 if (!first)
119 return false;
120 if (!first.template is<T>()) { // only allow same-type tuples/lists
121 CG_DEBUG("python:ObjectPtr:isVector")
122 << "Wrong object type unpacked from tuple/list: (python)" << first->ob_type->tp_name << " != (c++)"
123 << utils::demangle(typeid(T).name()) << ".";
124 return false;
125 }
126 return true;
127 }
128
129 template <typename T>
130 std::vector<T> ObjectPtr::vector() const {
131 if (!isVector<T>())
132 throw CG_ERROR("python::ObjectPtr:vector")
133 << "Object has invalid type: list/tuple != \"" << get()->ob_type->tp_name << "\".";
134 std::vector<T> vec;
135 const bool tuple = PyTuple_Check(get());
136 const Py_ssize_t num_entries = tuple ? PyTuple_Size(get()) : PyList_Size(get());
137 //--- check every single element inside the list/tuple
138 for (Py_ssize_t i = 0; i < num_entries; ++i) {
139 auto pit =
140 ObjectPtr::wrap(tuple ? PyTuple_GetItem(get(), i) /* borrowed */ : PyList_GetItem(get(), i) /* borrowed */);
141 if (!pit.is<T>())
142 throw CG_ERROR("python::ObjectPtr:vector") << "Mixed types detected in vector.";
143 vec.emplace_back(pit.value<T>());
144 }
145 return vec;
146 }
147
148 template <>
149 bool ObjectPtr::value<bool>() const {
150 CG_ASSERT(get());
151 return PyObject_IsTrue(get());
152 }
153
154 template <>
155 int ObjectPtr::value<int>() const {
156 if (!is<int>())
157 throw CG_ERROR("Python:get") << "Object has invalid type: integer != \"" << get()->ob_type->tp_name << "\".";
158#ifdef PYTHON2
159 return PyInt_AsLong(get());
160#else
161 return PyLong_AsLong(get());
162#endif
163 }
164
165 template <>
166 unsigned long ObjectPtr::value<unsigned long>() const {
167 if (!is<long>())
168 throw CG_ERROR("Python:get") << "Object has invalid type: unsigned long != \"" << get()->ob_type->tp_name
169 << "\".";
170#ifdef PYTHON2
171 return PyInt_AsUnsignedLongMask(get());
172#else
173 return PyLong_AsUnsignedLong(get());
174#endif
175 }
176
177 template <>
178 long long ObjectPtr::value<long long>() const {
179 if (!is<long>())
180 throw CG_ERROR("Python:get") << "Object has invalid type: long long != \"" << get()->ob_type->tp_name << "\".";
181 return PyLong_AsLongLong(get());
182 }
183
184 template <>
185 double ObjectPtr::value<double>() const {
186 if (!is<double>())
187 throw CG_ERROR("Python:get") << "Object has invalid type: double != \"" << get()->ob_type->tp_name << "\".";
188 return PyFloat_AsDouble(get());
189 }
190
191 template <>
192 std::string ObjectPtr::value<std::string>() const {
193 if (!is<std::string>())
194 throw CG_ERROR("Python:get") << "Object has invalid type: string != \"" << get()->ob_type->tp_name << "\".";
195#ifdef PYTHON2
196 return PyString_AsString(get());
197#else
198 if (PyUnicode_Check(get()))
199 return PyUnicode_AsUTF8(get());
200 else // if (PyBytes_Check(get()))
201 return strdup(PyBytes_AS_STRING(get()));
202#endif
203 }
204
205 template <>
206 Limits ObjectPtr::value<Limits>() const {
207 if (!is<Limits>())
208 throw CG_ERROR("Python:get") << "Object has invalid type: limits != \"" << get()->ob_type->tp_name << "\".";
209 const auto vec = vector<double>();
210 if (vec.size() == 1)
211 return Limits{vec.at(0)};
212 return Limits{vec.at(0), vec.at(1)};
213 }
214
215 template <>
216 ParametersList ObjectPtr::value<ParametersList>() const {
217 if (!is<ParametersList>())
218 throw CG_ERROR("Python:get") << "Object has invalid type: parameters list != \"" << get()->ob_type->tp_name
219 << "\".";
220 ParametersList out;
221 Py_ssize_t pos = 0;
222 PyObject *pkey{nullptr}, *pvalue{nullptr};
223 while (PyDict_Next(get(), &pos, &pkey, &pvalue)) {
224 const auto key = ObjectPtr::wrap(pkey), val = ObjectPtr::wrap(pvalue);
225 const std::string skey = key.is<std::string>() ? key.value<std::string>()
226 : key.is<int>() ? std::to_string(key.value<int>()) // integer-type key
227 : "invalid";
228 if (val.is<bool>())
229 out.set(skey, (bool)val.value<int>());
230 else if (val.is<int>())
231 out.set(skey, val.value<int>());
232 else if (val.is<double>())
233 out.set(skey, val.value<double>());
234 else if (val.is<std::string>())
235 out.set(skey, val.value<std::string>());
236 else if (val.is<ParametersList>())
237 out.set(skey, val.value<ParametersList>());
238 else if (PyTuple_Check(pvalue) || PyList_Check(pvalue)) { // vector
239 if (val.isVector<int>())
240 out.set(skey, val.vector<int>());
241 else if (val.isVector<double>()) {
242 if (val.is<Limits>())
243 out.set(skey, val.value<Limits>());
244 out.set(skey, val.vector<double>());
245 } else if (val.isVector<std::string>())
246 out.set(skey, val.vector<std::string>());
247 else if (val.isVector<Limits>())
248 out.set(skey, val.vector<Limits>());
249 else //if (val.isVector<ParametersList>())
250 out.set(skey, val.vector<ParametersList>());
251 } else if (pvalue == Py_None) {
252 out.set(skey, "None"s);
253 } else {
254 CG_WARNING("PythonTypes") << "Invalid object (" << pvalue->ob_type->tp_name << ") retrieved for key=" << skey
255 << " when unpacking a dictionary/parameters list.";
256 }
257 }
258 return out;
259 }
260
261 template <>
262 ObjectPtr ObjectPtr::make<PyObject*>(PyObject* const& obj) {
263 return ObjectPtr(obj);
264 }
265
266 template <>
267 ObjectPtr ObjectPtr::make<int>(const int& val) {
268#ifdef PYTHON2
269 return ObjectPtr(PyInt_FromLong(val));
270#else
271 return ObjectPtr(PyLong_FromLong(val));
272#endif
273 }
274
275 template <>
276 ObjectPtr ObjectPtr::make<bool>(const bool& val) {
277 return ObjectPtr(PyBool_FromLong(val));
278 }
279
280 template <>
281 ObjectPtr ObjectPtr::make<double>(const double& val) {
282 return ObjectPtr(PyFloat_FromDouble(val));
283 }
284
285 template <>
286 ObjectPtr ObjectPtr::make<std::string>(const std::string& val) {
287#ifdef PYTHON2
288 return ObjectPtr(PyString_FromString(val.c_str()));
289#else
290 return ObjectPtr(PyUnicode_FromString(val.c_str()));
291#endif
292 }
293
294 template <>
295 ObjectPtr ObjectPtr::make<Limits>(const Limits& val) {
296 return ObjectPtr::tupleFromVector(std::vector<double>{val.min(), val.max()});
297 }
298
299 template <>
300 ObjectPtr ObjectPtr::make<ParametersList>(const ParametersList& plist) {
301 ObjectPtr obj(PyDict_New());
302 for (const auto& key : plist.keys(true)) {
303 if (plist.has<bool>(key))
304 PyDict_SetItem(obj.get(), make(key).release(), make(plist.get<bool>(key)).release());
305 else if (plist.has<int>(key))
306 PyDict_SetItem(obj.get(), make(key).release(), make(plist.get<int>(key)).release());
307 else if (plist.has<double>(key))
308 PyDict_SetItem(obj.get(), make(key).release(), make(plist.get<double>(key)).release());
309 else if (plist.has<std::string>(key))
310 PyDict_SetItem(obj.get(), make(key).release(), make(plist.get<std::string>(key)).release());
311 else if (plist.has<ParametersList>(key))
312 PyDict_SetItem(obj.get(), make(key).release(), make(plist.get<ParametersList>(key)).release());
313 else if (plist.has<Limits>(key)) {
314 const auto& lim = plist.get<Limits>(key);
315 PyDict_SetItem(
316 obj.get(), make(key).release(), ObjectPtr::tupleFromVector<double>({lim.min(), lim.max()}).release());
317 } else if (plist.has<std::vector<int> >(key))
318 PyDict_SetItem(obj.get(),
319 make(key).release(),
320 ObjectPtr::tupleFromVector<int>(plist.get<std::vector<int> >(key)).release());
321 else if (plist.has<std::vector<double> >(key))
322 PyDict_SetItem(obj.get(),
323 make(key).release(),
324 ObjectPtr::tupleFromVector<double>(plist.get<std::vector<double> >(key)).release());
325 else if (plist.has<std::vector<std::string> >(key))
326 PyDict_SetItem(obj.get(),
327 make(key).release(),
328 ObjectPtr::tupleFromVector<std::string>(plist.get<std::vector<std::string> >(key)).release());
329 else
330 throw PY_ERROR << "Parameters list has an untranslatable object for key=" << key;
331 }
332 return obj;
333 }
334
335 template <typename T>
336 ObjectPtr ObjectPtr::tupleFromVector(const std::vector<T>& vec) {
337 ObjectPtr tuple(PyTuple_New(vec.size()));
338 for (size_t i = 0; i < vec.size(); ++i)
339 PyTuple_SetItem(tuple.get(), i, make<T>(vec.at(i)).release());
340 return tuple;
341 }
342
343 template <>
344 ObjectPtr ObjectPtr::tupleFromVector(const std::vector<PyObject*>& vec) {
345 ObjectPtr tuple(PyTuple_New(vec.size()));
346 for (size_t i = 0; i < vec.size(); ++i)
347 PyTuple_SetItem(tuple.get(), i, vec.at(i));
348 return tuple;
349 }
350
351 template ObjectPtr ObjectPtr::tupleFromVector<bool>(const std::vector<bool>&);
352 template ObjectPtr ObjectPtr::tupleFromVector<int>(const std::vector<int>&);
353 template ObjectPtr ObjectPtr::tupleFromVector<double>(const std::vector<double>&);
354 template ObjectPtr ObjectPtr::tupleFromVector<std::string>(const std::vector<std::string>&);
355 template ObjectPtr ObjectPtr::tupleFromVector<Limits>(const std::vector<Limits>&);
356
357 std::ostream& operator<<(std::ostream& os, const ObjectPtr& ptr) {
358 os << "PyObject{";
359 if (auto repr = ObjectPtr(PyObject_Str(ptr.get())); repr) // new
360 os << repr.value<std::string>();
361 return os << "}";
362 }
363
365 return ObjectPtr(PyObject_CallObject(get(), args.get()) /* new */);
366 }
367
368 ObjectPtr ObjectPtr::attribute(const std::string& attr) const {
369 if (PyObject_HasAttrString(get(), attr.c_str()) != 1)
370 return ObjectPtr(nullptr);
371 return ObjectPtr(PyObject_GetAttrString(get(), attr.c_str())); // new
372 }
373
374 ObjectPtr ObjectPtr::importModule(const std::string& mod_name) {
375 return ObjectPtr(PyImport_Import(ObjectPtr::make<std::string>(mod_name).get())); // new
376 }
377
378 ObjectPtr ObjectPtr::defineModule(const std::string& mod_name, const std::string& code) {
379 auto mod = ObjectPtr(PyImport_AddModule(mod_name.data()));
380 if (!mod)
381 throw PY_ERROR << "Failed to add the module.";
382 auto* local_dict = PyModule_GetDict(mod.get());
383 if (!local_dict)
384 throw PY_ERROR << "Failed to retrieve the local dictionary from module.";
385 ObjectPtr::wrap(PyRun_String(code.data(), Py_file_input, local_dict, local_dict));
386 CG_DEBUG("Python:defineModule") << "New '" << mod_name << "' module initialised from Python code parsing.\n"
387 << "List of attributes: "
388 << ObjectPtr(PyObject_Dir(mod.get())).vector<std::string>() << ".";
389 return mod;
390 }
391 } // namespace python
392} // namespace cepgen
#define PY_ERROR
Definition Error.h:25
#define CG_ERROR(mod)
Definition Exception.h:60
#define CG_ASSERT(assertion)
Definition Exception.h:62
#define CG_WARNING(mod)
Definition Message.h:228
#define CG_DEBUG(mod)
Definition Message.h:220
Validity interval for a variable.
Definition Limits.h:28
double min() const
Lower limit to apply on the variable.
Definition Limits.h:52
double max() const
Upper limit to apply on the variable.
Definition Limits.h:54
bool has(const std::string &key) const
Check if a given parameter is handled in this list.
std::vector< std::string > keys(bool name_key=true) const
T get(const std::string &key, const T &def=default_arg< T >::get()) const
Get a parameter value.
ParametersList & set(const std::string &, const T &)
Set a parameter value Set a recast parameter value.
Smart pointer to a Python object and its dereferencing operator.
Definition ObjectPtr.h:36
bool isVector() const
Check if a Python object is compatible with a vector of uniform objects.
ObjectPtr attribute(const std::string &) const
Retrieve the attribute from a python object.
std::vector< T > vector() const
Retrieve a vector of objects, either from a Python list or tuple.
static ObjectPtr importModule(const std::string &)
Import a Python module in a new reference-counted Python object.
static ObjectPtr wrap(PyObject *)
Wrap a PyObject without cleaning at the destructor.
Definition ObjectPtr.cpp:45
static ObjectPtr defineModule(const std::string &, const std::string &)
Define a Python module from a Python code in a new reference-counted Python object.
static ObjectPtr tuple(const std::tuple< Args... > &c_tuple)
Build a Python tuple from a C++ tuple.
Definition ObjectPtr.h:77
ObjectPtr(PyObject *, bool wrap_only=false)
Definition ObjectPtr.cpp:42
static ObjectPtr make(const T &)
Build a new Python object from a C++ one.
ObjectPtr call(const ObjectPtr &) const
Call a python function with a tuple of arguments.
static ObjectPtr tupleFromVector(const std::vector< T > &)
Build a Python tuple from a (uniform) vector of objects.
bool ObjectPtr::is< std::string >() const
Definition ObjectPtr.cpp:83
std::unique_ptr< PyObject, void(*)(PyObject *)> PyObjectPtr
Definition ObjectPtr.h:34
std::ostream & operator<<(std::ostream &os, const ObjectPtr &ptr)
std::string ObjectPtr::value< std::string >() const
void obj_deleter(PyObject *obj)
Definition ObjectPtr.cpp:30
static std::string repr(const ParametersList &params, const std::string &key)
std::string demangle(const char *name)
Demangle a type id if possible.
Definition String.cpp:341
Common namespace for this Monte Carlo generator.