BornAgain  1.19.79
Open-source research software to simulate and fit neutron and x-ray reflectometry and grazing-incidence small-angle scattering
PyUtils.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file Base/Py/PyUtils.cpp
6 //! @brief IOmplements various functions from PyUtils namespace
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************************************
14 
15 #ifdef BORNAGAIN_PYTHON
16 
17 #include "Base/Py/PyUtils.h"
18 #include "Base/Py/PyCore.h"
19 #include "Base/Util/SysUtils.h"
20 #include <iostream>
21 #include <sstream>
22 #include <stdexcept>
23 
25 {
26  std::string result;
27  PyObject* pyStr = PyUnicode_AsEncodedString(obj, "utf-8", "replace");
28  if (pyStr != nullptr) {
29  result = std::string(PyBytes_AsString(pyStr));
30  Py_DecRef(pyStr);
31  }
32  return result;
33 }
34 
35 std::vector<std::string> BaseUtils::Python::toVectorString(PyObject* obj)
36 {
37  std::vector<std::string> result;
38 
39  if (PyTuple_Check(obj)) {
40  for (Py_ssize_t i = 0; i < PyTuple_Size(obj); i++) {
41  PyObject* value = PyTuple_GetItem(obj, i);
42  result.push_back(toString(value));
43  }
44  } else if (PyList_Check(obj)) {
45  for (Py_ssize_t i = 0; i < PyList_Size(obj); i++) {
46  PyObject* value = PyList_GetItem(obj, i);
47  result.push_back(toString(value));
48  }
49  } else
50  throw std::runtime_error(
51  "BaseUtils::Python::toVectorString() -> Error. Unexpected object.");
52 
53  return result;
54 }
55 
56 std::string BaseUtils::Python::toString(char* c)
57 {
58  if (c)
59  return c;
60  return "";
61 }
62 
63 std::string BaseUtils::Python::toString(wchar_t* c)
64 {
65  if (!c)
66  return "";
67  std::wstring wstr(c);
68  return std::string(wstr.begin(), wstr.end());
69 }
70 
71 PyObject* BaseUtils::Python::import_bornagain(const std::vector<std::string>& paths)
72 {
73  if (Py_IsInitialized())
74  return nullptr;
75 
76  Py_InitializeEx(0); // like Py_Initialize, but skip registration of signal handlers
77 
78  if (!paths.empty()) {
79  PyObject* sysPath = PySys_GetObject((char*)"path");
80  for (const std::string& path : paths)
81  PyList_Append(sysPath, PyString_FromString(path.c_str()));
82  }
83 
84  // Stores signal handler before numpy's mess it up.
85  // This is to make ctrl-c working from terminal.
86 #ifndef _WIN32
87  PyOS_sighandler_t sighandler;
88  sighandler = PyOS_getsig(SIGINT);
89 #endif
90 
91  PyObject* pmod = PyImport_ImportModule("bornagain");
92  if (!pmod) {
93  PyErr_Print();
94  throw std::runtime_error("Can't load bornagain");
95  }
96 
97  // restores single handler to make ctr-c alive.
98 #ifndef _WIN32
99  PyOS_setsig(SIGINT, sighandler);
100 #endif
101 
102  return pmod;
103 }
104 
106 {
107  Py_InitializeEx(0);
108 
109  std::stringstream result;
110 
111  // Runtime environment
112  result << std::string(60, '=') << "\n";
113  result << "PATH: " << BaseUtils::System::getenv("PATH") << "\n";
114  result << "PYTHONPATH: " << BaseUtils::System::getenv("PYTHONPATH") << "\n";
115  result << "PYTHONHOME: " << BaseUtils::System::getenv("PYTHONHOME") << "\n";
116 
117  // Embedded Python details
118  result << "Py_GetProgramName(): " << BaseUtils::Python::toString(Py_GetProgramName()) << "\n";
119  result << "Py_GetProgramFullPath(): " << BaseUtils::Python::toString(Py_GetProgramFullPath())
120  << "\n";
121  result << "Py_GetPath(): " << BaseUtils::Python::toString(Py_GetPath()) << "\n";
122  result << "Py_GetPythonHome(): " << BaseUtils::Python::toString(Py_GetPythonHome()) << "\n";
123 
124  // Runtime Python's sys.path
125  PyObject* sysPath = PySys_GetObject((char*)"path");
126  auto content = BaseUtils::Python::toVectorString(sysPath);
127  result << "sys.path: ";
128  for (auto s : content)
129  result << s << ",";
130  result << "\n";
131 
132  return result.str();
133 }
134 
135 // Attempt to retrieve Python stack trace
136 // https://stackoverflow.com/questions/1796510/accessing-a-python-traceback-from-the-c-api
137 
139 {
140  std::stringstream result;
141 
142  if (PyErr_Occurred()) {
143  PyObject *ptype, *pvalue, *ptraceback, *pystr;
144 
145  PyErr_Fetch(&ptype, &pvalue, &ptraceback);
146  pystr = PyObject_Str(pvalue);
147  if (char* str = PyString_AsString(pystr))
148  result << std::string(str) << "\n";
149 
150  PyObject* module_name = PyString_FromString("traceback");
151  PyObject* pyth_module = PyImport_Import(module_name);
152  Py_DecRef(module_name);
153 
154  if (pyth_module) {
155  result << "\n";
156  PyObject* pyth_func = PyObject_GetAttrString(pyth_module, "format_exception");
157  if (pyth_func && PyCallable_Check(pyth_func)) {
158  PyObject* pyth_val =
159  PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL);
160  if (pyth_val) {
161  pystr = PyObject_Str(pyth_val);
162  if (char* str = PyString_AsString(pystr))
163  result << std::string(str);
164  Py_DecRef(pyth_val);
165  }
166  }
167  result << "\n";
168  }
169  }
170 
171  result << "\n";
172  result << pythonRuntimeInfo();
173  result << "\n";
174 
175  return result.str();
176 }
177 
178 PyObject* BaseUtils::Python::createNumpyArray(const std::vector<double>& data)
179 {
180  const size_t ndim(1);
181  npy_int ndim_numpy = ndim;
182  auto* ndimsizes_numpy = new npy_intp[ndim];
183  ndimsizes_numpy[0] = data.size();
184 
185  // creating standalone numpy array
186  PyObject* pyarray = PyArray_SimpleNew(ndim_numpy, ndimsizes_numpy, NPY_DOUBLE);
187  delete[] ndimsizes_numpy;
188  if (pyarray == nullptr)
189  throw std::runtime_error("ExportDatafield() -> Panic in PyArray_SimpleNew");
190 
191  // getting pointer to data buffer of numpy array
192  double* array_buffer = (double*)PyArray_DATA((PyArrayObject*)pyarray);
193 
194  for (size_t index = 0; index < data.size(); ++index)
195  *array_buffer++ = data[index];
196 
197  return pyarray;
198 }
199 
200 #endif // BORNAGAIN_PYTHON
Includes python header and takes care of warnings.
_object PyObject
Definition: PyObject.h:25
Defines PyUtils namespace.
Defines various stuff in namespace Utils.
std::string pythonRuntimeInfo()
Returns multi-line string representing PATH, PYTHONPATH, sys.path and other info.
Definition: PyUtils.cpp:105
std::vector< std::string > toVectorString(PyObject *obj)
Converts PyObject into vector of strings, if possible, or throws exception.
Definition: PyUtils.cpp:35
PyObject * import_bornagain(const std::vector< std::string > &paths={})
Imports BornAgain from given location. If path is empty, tries to rely on PYTHONPATH.
Definition: PyUtils.cpp:71
std::string toString(PyObject *obj)
Converts PyObject into string, if possible, or throws exception.
Definition: PyUtils.cpp:24
PyObject * createNumpyArray(const std::vector< double > &data)
Definition: PyUtils.cpp:178
std::string pythonStackTrace()
Returns string representing python stack trace.
Definition: PyUtils.cpp:138
std::string getenv(const std::string &name)
Returns environment variable.
Definition: SysUtils.cpp:32