From 4e3c8e155a7a3dc08815e68bfd6223362f1a8b59 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 1 Feb 2022 14:56:27 +0100 Subject: [PATCH 01/85] PyCore: The header needed for using Python and Numpy C-API --- PyTools/Embed/PyCore.h | 57 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 PyTools/Embed/PyCore.h diff --git a/PyTools/Embed/PyCore.h b/PyTools/Embed/PyCore.h new file mode 100644 index 00000000000..933c85ea937 --- /dev/null +++ b/PyTools/Embed/PyCore.h @@ -0,0 +1,57 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file PyTools/Embed/PyCore.h +//! @brief Includes python header and takes care of warnings. +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifdef SWIG +#error no need to expose this header to Swig +#endif + +#ifndef USER_API +#ifndef BORNAGAIN_PYTOOLS_PYCORE_H +#define BORNAGAIN_PYTOOLS_PYCORE_H + +#ifdef BORNAGAIN_PYTHON +#undef _POSIX_C_SOURCE +#undef _XOPEN_SOURCE + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#define PY_SSIZE_T_CLEAN +#include <Python.h> + +#ifdef INCLUDE_NUMPY + +#ifdef BORNAGAIN_PYTHON_API_SWIG +/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` and `NO_IMPORT_ARRAY` one can use + the Numpy Array API in multiple files for a single extension module. +*/ +#define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY +#define NO_IMPORT_ARRAY +#endif // BORNAGAIN_PYTHON_API_SWIG + +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION // to prevent spurious warning +#include <numpy/arrayobject.h> + +#endif // INCLUDE_NUMPY + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif // BORNAGAIN_PYTHON +#endif // BORNAGAIN_PYTOOLS_PYCORE_H +#endif // USER_API -- GitLab From 1cf631a52e811890408545a08111596d666aede6 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 1 Feb 2022 14:56:39 +0100 Subject: [PATCH 02/85] PyObjectDecl: Forward declaration of PyObject This header is used when a module needs `PyObject*`, or `np_size_t` (Numpy). --- PyTools/Embed/PyObjectDecl.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 PyTools/Embed/PyObjectDecl.h diff --git a/PyTools/Embed/PyObjectDecl.h b/PyTools/Embed/PyObjectDecl.h new file mode 100644 index 00000000000..8fdd48f8d38 --- /dev/null +++ b/PyTools/Embed/PyObjectDecl.h @@ -0,0 +1,16 @@ +// forward declarations needed for using `PyObject` + +#ifndef USER_API + +#ifndef PYOBJECT_H +#define PYOBJECT_H + +#ifndef PyObject_HEAD +struct _object; +typedef _object PyObject; +#endif + +typedef long int np_size_t; // size type used in Numpy + +#endif // PYOBJECT_H +#endif // USER_API -- GitLab From bf1c5285f3529bb8338079e039c07cf27f064974 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 1 Feb 2022 14:56:59 +0100 Subject: [PATCH 03/85] Result: A container for returned results which may fail The class `Result` contains a value (or none) and a `Status` which denotes the status of the value (OK/Warning/Error). This is used to avoid throwing exceptions in a module, leaving the decision to the callers whether an exception is needed or not. --- PyTools/Embed/Result.cpp | 36 +++++++++++ PyTools/Embed/Result.h | 131 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 PyTools/Embed/Result.cpp create mode 100644 PyTools/Embed/Result.h diff --git a/PyTools/Embed/Result.cpp b/PyTools/Embed/Result.cpp new file mode 100644 index 00000000000..2b32315670f --- /dev/null +++ b/PyTools/Embed/Result.cpp @@ -0,0 +1,36 @@ +#include "Result.h" + +Status::Status(const Status::Type type, const std::string& message): + type{type}, + message{message} {} + +bool Status::isSet() const +{ + return (type != Status::Type::None); +} + + +bool Status::isOK() const +{ + return (type == Status::Type::Info); +} + + +bool Status::isWarning() const +{ + return (type == Status::Type::Warning); +} + + +bool Status::isError() const +{ + return (type == Status::Type::Error); +} + + +Status::operator bool() const +{ + return isOK() || isWarning(); +} + +//======================================== diff --git a/PyTools/Embed/Result.h b/PyTools/Embed/Result.h new file mode 100644 index 00000000000..73f752fbfe0 --- /dev/null +++ b/PyTools/Embed/Result.h @@ -0,0 +1,131 @@ +#ifndef RESULT_H +#define RESULT_H + +#include <utility> // move +#include <string> + +// ======================================== + +// Indicator for the status of a result +struct Status +{ + enum class Type {None=-1, Info, Warning, Error}; + Type type = Type::None; + + // status message + std::string message; + + Status(const Status::Type type = Type::None, + const std::string& message = ""); + + bool isSet() const; + + bool isOK() const; + + bool isWarning() const; + + bool isError() const; + + // cast to boolean (true if result is valid) + operator bool() const; +}; + +//======================================== + +// Container for Results which may fail +/* A `Result<T>` instance contains a value of type `T` (or none) and + a `Status` which denotes the status of the value (OK/Warning/Error). + This is used to avoid throwing exceptions in a _core_ module, + leaving the decision to the callers (or the higher interface) whether + an exception should be raised or not. +*/ +template <typename ResType> +class Result +{ + +public: + + using type = ResType; + + // stored result + ResType val; + + // status of the PyObject + Status status; + + Result(ResType&& result, Status&& status); + + Result(ResType&& result); + + Result(Status&& status); + + ~Result(); + + // disallow copy constructor + Result(const Result&) = delete; + // disallow copy assignment + Result& operator=(const Result&) = delete; + // allow move constructor + Result(Result&& other); + // reset the container to the initial status + void reset(); + // check validity of the Result + bool valid() const; + // check if the Result is set + bool isSet() const; +}; + + +//-- Implementation of Result + +template <typename ResType> +Result<ResType>::Result(ResType&& result, + Status&& status): + val{std::move(result)}, + status{std::move(status)} {} + + +template <typename ResType> +Result<ResType>::Result(ResType&& result): + Result(std::move(result), Status(Status::Type::Info)) {} + + +template <typename ResType> +Result<ResType>::Result(Status&& status): + status{std::move(status)} {} + + +template <typename ResType> +Result<ResType>::~Result() {} + + +template <typename ResType> +Result<ResType>::Result(Result<ResType>&& other): + Result(std::move(other.val), std::move(other.status)) +{ + // reset the moved object + other.reset(); +} + + +template <typename ResType> +void Result<ResType>::reset() +{ + status = Status(); +} + + +template <typename ResType> +bool Result<ResType>::valid() const +{ + return (status.isOK() || status.isWarning()); +} + + +template <typename ResType> +bool Result<ResType>::isSet() const +{ + return (status.isSet()); +} + +#endif // RESULT_H -- GitLab From ee8b54438171e5706169a086545e2aa699683752 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 1 Feb 2022 14:56:50 +0100 Subject: [PATCH 04/85] PyObjectPtr: Container for the results from the embedded Python interpreter The class `PyObjectPtr` contains a `PyObject*` (or `NULL`) and a `Status` which denotes the status of the value (OK/Warning/Error). Decrementing Python reference-count is performed automatically when a `PyObjectPtr` expires. --- PyTools/Embed/PyObjectPtr.cpp | 83 +++++++++++++++++++++++++++++++++++ PyTools/Embed/PyObjectPtr.h | 72 ++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 PyTools/Embed/PyObjectPtr.cpp create mode 100644 PyTools/Embed/PyObjectPtr.h diff --git a/PyTools/Embed/PyObjectPtr.cpp b/PyTools/Embed/PyObjectPtr.cpp new file mode 100644 index 00000000000..55390c99ab5 --- /dev/null +++ b/PyTools/Embed/PyObjectPtr.cpp @@ -0,0 +1,83 @@ +#include "PyObjectPtr.h" +#include "PythonInterpreter.h" + +#include <string> +#include <sstream> // stringstream +#include <exception> + +PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr, + const Status& status): + ptr{pyobject_ptr}, + status{status} +{ + // if PyObject pointer is null but result type is not `None` or `Error`, + // then that will be an `Error` case. + if(!ptr && status.type != Status::Type::None + && status.type != Status::Type::Error) + { + this->status.type = Status::Type::Error; + } +} + + +PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr): + PyObjectPtr(pyobject_ptr, Status(Status::Type::Info)) {} + + +PyObjectPtr::PyObjectPtr(const Status& status): + PyObjectPtr(nullptr, status) {} + + +PyObjectPtr::~PyObjectPtr() +{ + discard(); +} + + +PyObjectPtr::PyObjectPtr(PyObjectPtr&& other): + PyObjectPtr(other.ptr, other.status) +{ + // reset the moved object + other.reset(); +} + + +PyObject* PyObjectPtr::release() +{ + PyObject* pyobject_ptr {ptr}; + reset(); + return pyobject_ptr; +} + + +void PyObjectPtr::reset() +{ + ptr = nullptr; + status = Status(); +} + + +void PyObjectPtr::discard() +{ + if (!PythonInterpreter::isInitialized()) { + throw(std::runtime_error( + "Decrementing Python reference-count without " + "Python initialized leads to memory access violation " + "(segmentation fault)")); + } + + PythonInterpreter::DecRef(ptr); + reset(); +} + + +bool PyObjectPtr::valid() const +{ + return (ptr && (status.isOK() || status.isWarning())); +} + + +bool PyObjectPtr::isSet() const +{ + return (ptr && status.isSet()); +} diff --git a/PyTools/Embed/PyObjectPtr.h b/PyTools/Embed/PyObjectPtr.h new file mode 100644 index 00000000000..2165b64770a --- /dev/null +++ b/PyTools/Embed/PyObjectPtr.h @@ -0,0 +1,72 @@ +#ifndef PYOBJECTPTR_H +#define PYOBJECTPTR_H + +#include "PyObjectDecl.h" +#include "Result.h" // Status +#include <vector> + +//======================================== + +// safe container for PyObjects +/* +The class `PyObjectPtr` contains a `PyObject*` (or `NULL`) and a `Status` which denotes the status of the value (OK/Warning/Error). +Decrementing Python reference-count is performed automatically when a +`PyObjectPtr` expires. +*/ + +class PyObjectPtr +{ + +public: + // raw pointer to the PyObject + PyObject* ptr = nullptr; + + // status of the PyObject + Status status; + + PyObjectPtr(PyObject* pyobject_ptr, const Status& status); + + PyObjectPtr(PyObject* pyobject_ptr); + + PyObjectPtr(const Status& status); + + ~PyObjectPtr(); + + // disallow copy constructor + PyObjectPtr(const PyObjectPtr&) = delete; + // disallow copy assignment + PyObjectPtr& operator=(const PyObjectPtr&) = delete; + // allow move constructor + PyObjectPtr(PyObjectPtr&& other); + // reset the container to the initial status (does _not_ release the Python resource) + void reset(); + // reset the container to the initial status and return the PyObject pointer (does _not_ release the Python resource) + PyObject* release(); + // discards the Python resource (decrements the Python reference) + void discard(); + // check validity of the PyObjectPtr + bool valid() const; + // check if the PyObjectPtr is set + bool isSet() const; +}; + + +// Numpy array descriptor +struct NpArrayDescr +{ + // Numpy array descriptors + bool C_contiguous = false; + bool F_contiguous = false; + // whether the data area of arr is aligned and in machine byte-order + bool wellbehaved = false; + // number of dimensions + int n_dims = 0; + // sizes of dimensions + std::vector<np_size_t> dims; + // character code indicating the data type + char dtype = '\0'; + // whether Numpy array owns its data + bool owns_data = false; +}; + +#endif // PYOBJECTPTR_H -- GitLab From 1e6024d1656382b381a7a176d8fc59098ad45e02 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 1 Feb 2022 14:57:10 +0100 Subject: [PATCH 05/85] PythonInterpreter: Embedded Python functionality The embedded Python and Numpy functionality is implemented in a self-contained module. The stable part of the Python C-API is marked. The compatibility across Python 3.7-3.9 is ensured. --- PyTools/Embed/PythonInterpreter.cpp | 953 ++++++++++++++++++++++++++++ PyTools/Embed/PythonInterpreter.h | 103 +++ 2 files changed, 1056 insertions(+) create mode 100644 PyTools/Embed/PythonInterpreter.cpp create mode 100644 PyTools/Embed/PythonInterpreter.h diff --git a/PyTools/Embed/PythonInterpreter.cpp b/PyTools/Embed/PythonInterpreter.cpp new file mode 100644 index 00000000000..bae6070c731 --- /dev/null +++ b/PyTools/Embed/PythonInterpreter.cpp @@ -0,0 +1,953 @@ +/* Embedded Python Interpreter + +Note that Python objects are structures allocated on the heap, +accessed through pointers of type `PyObject*`. + +References: +- Python C-API <https://docs.python.org/3/c-api> +- Python ABI stability <https://docs.python.org/3/c-api/stable.html> +- Numpy C-API <https://numpy.org/doc/stable/reference/c-api>; + <https://numpy.org/doc/stable/reference/c-api/array.html>. +- Python Extension Patterns <https://pythonextensionpatterns.readthedocs.io> +- "Python behind the scenes" series <https://tenthousandmeters.com/tag/python-behind-the-scenes> +- Python's garbage collector <https://rushter.com/blog/python-garbage-collector> +*/ + +#define INCLUDE_NUMPY // also include Numpy via PyCore +#include "PyCore.h" +#undef INCLUDE_NUMPY + +#include "PythonInterpreter.h" + +#include <cstddef> // NULL +#include <cstring> // memcpy +#include <string> +#include <sstream> +#include <vector> +#include <memory> // unique_ptr + +#include <iostream> // DEBUG +//======================================== + + +namespace { +//-- Auxiliary functions + +// thin wrapper to initialize Numpy +int _init_numpy() +{ + if(!PyArray_API) { + /* To use Numpy Array C-API from an extension module, + `import_array` function must be called. + If the extension module is self-contained in a single .c file, + then that is all that needs to be done. + This function imports the module where the function-pointer table + is stored and points the correct variable to it. + */ + // NOTE: import_array returns `NULL` on failure. + import_array(); + return 1; // Numpy Array API loaded successfully + } + + return 2; // Numpy Array API is already loaded +} + + +//! Converts char to string. In the case of nullptr will return an empty string +std::string toString(const char* const c) +{ + if (c) + return c; + return ""; +} + + +std::string toString(const wchar_t* const c) +{ + if (!c) + return ""; + std::wstring wstr(c); + return std::string(wstr.begin(), wstr.end()); +} + +//! Converts PyObject into vector of strings, if possible, or throws exception +Result<std::vector<std::string>> toVectorString(PyObject* py_object) +{ + std::vector<std::string> result; + + if (PyTuple_Check(py_object)) { + for (Py_ssize_t i = 0; i < PyTuple_Size(py_object); i++) { + PyObject* value = PyTuple_GetItem(py_object, i); + result.push_back(PythonInterpreter::pyStrtoString(value)); + } + } else if (PyList_Check(py_object)) { + for (Py_ssize_t i = 0; i < PyList_Size(py_object); i++) { + PyObject* value = PyList_GetItem(py_object, i); + result.push_back(PythonInterpreter::pyStrtoString(value)); + } + } else + return {Status(Status::Type::Error, + "Cannnot convert the given Python object " + "to vector<string>.")}; + return {std::move(result)}; +} + + +// auxiliary function to convert a Numpy array data of real type `real_t` +// to vector<double>. +template<typename real_t> +inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, + npy_intp rowIdxMax, npy_intp colIdxMax, + std::vector<double>& vector_out) +{ + std::size_t idx = 0; + for(npy_intp i_row = 0; i_row < rowIdxMax; ++i_row) { + for(npy_intp j_col = 0; j_col < colIdxMax; ++j_col) { + const double val = static_cast<double>( + *reinterpret_cast<real_t*>(PyArray_GETPTR2(npArray_ptr, i_row, j_col))); + vector_out[idx] = val; + ++idx; + } + } +} + + +double* npyArray_to_CArray_WIP(PyObjectPtr& npy_array) +{ + if(!npy_array.valid() || !PyArray_Check(npy_array.ptr)) { + throw std::runtime_error( + "PythonInterpreter: Cannot convert an invalid Numpy array to a C-Array"); + } + + PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(npy_array.ptr)}; + PyArray_Descr* npArray_descr = PyArray_DESCR(npArray_ptr); + const int npArray_type = PyArray_TYPE(npArray_ptr); + const int npArray_ndim = PyArray_NDIM(npArray_ptr); + npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); + const int npArray_flags = PyArray_FLAGS(npArray_ptr); + + // DEBUG + // type checking + if(PyDataType_ISUNSIGNED(npArray_descr)) + printf("AN>> Array datatype = uint\n"); + else if(PyDataType_ISSIGNED(npArray_descr)) + printf("AN>> Array datatype = int\n"); + else if(PyDataType_ISFLOAT(npArray_descr)) + printf("AN>> Array datatype = float\n"); + else + printf("AN>> Array datatype = ?\n"); + + if(npArray_type == NPY_FLOAT) + printf("AN>> Array type (%d) = f32\n", npArray_type); + + if(PyArray_IS_C_CONTIGUOUS(npArray_ptr)) + printf("AN>> Array is C-contiguous (row-major)\n"); + + if(PyArray_IS_F_CONTIGUOUS(npArray_ptr)) + printf("AN>> Array is Fortran-contiguous (column-major)\n"); + + if(PyArray_ISBEHAVED_RO(npArray_ptr) || PyArray_ISBEHAVED(npArray_ptr)) + printf("AN>> Array is well-behaved\n"); + + if(npArray_flags & NPY_ARRAY_OWNDATA) + printf("AN>> Array owns its data\n"); + else + printf("AN>> Array does not own its data\n"); + + for(int i_dim = 0; i_dim < npArray_ndim; ++i_dim) + printf("AN>> Array-dim{%d} = %ld\n", i_dim, npArray_dims[i_dim]); + + // END DEBUG + + // TODO: Verify array is 2d + + // void *PyArray_GETPTR2(PyArrayObject *obj, npy_intp i, npy_intp j) + // Quick, inline access to the element at the given coordinates in the ndarray, obj, which must have 2 dimensions (this is not checked). The corresponding i, j, coordinates can be any integer but will be interpreted as npy_intp. You may want to typecast the returned pointer to the data type of the ndarray. + + // element-by-element copy of the Numpy array data (always work) + const std::size_t vsize = static_cast<std::size_t>(PyArray_SIZE(npArray_ptr)); + std::vector<double> out1(vsize); + for (std::size_t i = 0; i < vsize; i++) { + out1[i] = *reinterpret_cast<double*>(PyArray_GETPTR1(npArray_ptr, i)); + } + + // using bulk memcpy-like operation, but keep in mind that NumPy ndarrays may be mis-aligned for the data type, have non-native byte order, or other subtle attributes that make such copies less than desirable. + + std::vector<double> out2(vsize); + std::memcpy(out2.data(), PyArray_DATA(npArray_ptr), + sizeof(double) * vsize); + + // using either approach, out now contains a copy of the ndarray's data + + double* npArray_data_ptr {reinterpret_cast<double*>(PyArray_DATA(npArray_ptr))}; + if(!npArray_data_ptr) { + // checkError(); + throw std::runtime_error("PythonInterpreter: Cannot convert an invalid Numpy array to a C-Array (invalid data pointer)"); + } + + // TODO: Problem with the reference to numpy array!!! + // returns a _new_ reference; ie. caller is responsible for the Python ref-count + return npArray_data_ptr; +} + + +Result<double*> NpyArray1D_to_C(PyObject* pyobject_ptr) +{ + if(!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: Cannot convert an invalid " + "Numpy array to a C-Array")}; + } + + PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; + PyArray_Descr* npArray_descr = PyArray_DTYPE(npArray_ptr); + const int npArray_type = PyArray_TYPE(npArray_ptr); + const int npArray_ndim = PyArray_NDIM(npArray_ptr); + npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); + const int npArray_flags = PyArray_FLAGS(npArray_ptr); + // character code indicating the data type + const char npArray_dtype = npArray_descr->type; + + //TODO: Enable type-casting for the Numpy array + if(npArray_type != NPY_DOUBLE) { + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::Numpy: " + << "Expected a Numpy array of type _double_ " + << "(given type '" << npArray_dtype << "')"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // DEBUG + // type checking + if(PyDataType_ISUNSIGNED(npArray_descr)) + printf("AN>> Array datatype = uint\n"); + else if(PyDataType_ISSIGNED(npArray_descr)) + printf("AN>> Array datatype = int\n"); + else if(PyDataType_ISFLOAT(npArray_descr)) + printf("AN>> Array datatype = float\n"); + else + printf("AN>> Array datatype = ?\n"); + + if(npArray_type == NPY_FLOAT) + printf("AN>> Array type (%d) = f32\n", npArray_type); + + if(PyArray_IS_C_CONTIGUOUS(npArray_ptr)) + printf("AN>> Array is C-contiguous (row-major)\n"); + + if(PyArray_IS_F_CONTIGUOUS(npArray_ptr)) + printf("AN>> Array is Fortran-contiguous (column-major)\n"); + + if(PyArray_ISBEHAVED_RO(npArray_ptr) || PyArray_ISBEHAVED(npArray_ptr)) + printf("AN>> Array is well-behaved\n"); + + if(npArray_flags & NPY_ARRAY_OWNDATA) + printf("AN>> Array owns its data\n"); + else + printf("AN>> Array does not own its data\n"); + + for(int i_dim = 0; i_dim < npArray_ndim; ++i_dim) + printf("AN>> Array-dim{%d} = %ld\n", i_dim, npArray_dims[i_dim]); + + // END DEBUG + + // TODO: check if array is contiguous + double* npArray_data_ptr {reinterpret_cast<double*>(PyArray_DATA(npArray_ptr))}; + if(!npArray_data_ptr) { + PythonInterpreter::checkError(); + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: " + "Cannot convert an invalid Numpy array to a C-Array" + "(data pointer is invalid)")}; + } + + return {std::move(npArray_data_ptr)}; +} + + +PyObjectPtr call_pyfunction_npy_array( + PyObjectPtr& py_module, const std::string& fn_name, PyObjectPtr& npy_array) +{ + // Call a Python function (from a given module) which takes a + // single Numpy array as input and returns a Numpy array. + + if (!py_module.valid()) { + return {Status(Status::Type::Error, + "PythonInterpreter: Cannot call a function " + "from an invalid Python module")}; + } + + PyObject* pFunc = PyObject_GetAttrString(py_module.ptr, fn_name.c_str()); + if (!pFunc || !PyCallable_Check(pFunc)) { + PythonInterpreter::checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter: The function '" << fn_name << "' " + << "is not callable"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + PyObject* pReturn = PyObject_CallFunctionObjArgs(pFunc, npy_array.ptr, + NULL); + if (!PyArray_Check(pReturn)) { + PythonInterpreter::checkError(); + Py_DecRef(pFunc); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter: Invalid return value from calling " + "the function '" << fn_name << "' (expected a Numpy array)"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // returns a _new_ reference; ie. caller is responsible for the ref-count + return {pReturn}; +} + +} // namespace +//---------------------------------------- + +// Python stable ABI +Status PythonInterpreter::initialize() +{ + std::stringstream msg_ss; + + if (!Py_IsInitialized()) { + Py_Initialize(); + msg_ss << "PythonInterpreter: Initialized Python - " + << "version: " << Py_GetVersion(); + return {Status::Type::Info, msg_ss.str()}; + + } + + // if Python already initialized + msg_ss << "PythonInterpreter: Python already initialized - " + << "version: " << Py_GetVersion(); + return {Status::Type::Warning, msg_ss.str()}; +} + +// Python stable ABI +bool PythonInterpreter::isInitialized() +{ + return static_cast<bool>(Py_IsInitialized()); +} + + +// Python stable ABI +Status PythonInterpreter::finalize() +{ + // undo all initializations made by Py_Initialize() and subsequent use + // of Python/C API functions, and destroy all sub-interpreters. + // This is a no-op when called for a second time. + Py_Finalize(); + + std::stringstream msg_ss; + msg_ss << "PythonInterpreter: Finalized Python - " + << "version: " << Py_GetVersion(); + return {Status::Type::Info, msg_ss.str()}; +} + + +// Python stable ABI +Status PythonInterpreter::checkError() +{ + if (PyErr_Occurred()) { + // print a standard traceback to sys.stderr and clear the error indicator + std::cerr << "---PythonInterpreter: Error in Python interpreter:\n"; + PyErr_Print(); + std::cerr << "\n---\n"; + return {Status::Type::Error}; + } + + return {Status::Type::Info, "No Python errors occured"}; +} + + +// Python stable ABI +void PythonInterpreter::addPythonPath(const std::string& path) +{ + if (!path.empty()) { + // add path to `PYTHONPATH` + PyObject* sysPath = PySys_GetObject((char*)"path"); + PyList_Append(sysPath, PyUnicode_FromString(path.c_str())); + } +} + +// Python stable ABI +int PythonInterpreter::setPythonPath(const std::string& path) +{ + // returns 0 on success, -1 on error + const int result = PySys_SetObject( + (char*)"path", PyUnicode_FromString(path.c_str())); + return result; +} + +// Python stable ABI +PyObjectPtr PythonInterpreter::import( + const std::string& pymodule_name, const std::string& path) +{ + addPythonPath(path); + + // import the module + PyObject* pymodule = PyImport_ImportModule(pymodule_name.c_str()); + if (!pymodule || !PyModule_Check(pymodule)) { + checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter: Cannot load Python module " + << "'" << pymodule_name << "' " + << "(given path = '" << path << "')"; + return PyObjectPtr(Status(Status::Type::Error, msg_ss.str())); + } + + // returns a _new_ reference; ie. caller is responsible for the ref-count + return {pymodule}; +} + + +// Python stable ABI +void PythonInterpreter::DecRef(PyObject* py_object) +{ + Py_XDECREF(py_object); +} + + +std::string PythonInterpreter::pyStrtoString(PyObject* py_object) +{ + std::string result; + PyObject* pyStr = PyUnicode_AsEncodedString(py_object, "utf-8", "replace"); + if (pyStr) { + result = std::string(PyBytes_AsString(pyStr)); + Py_DecRef(pyStr); + } + + return result; +} + + +std::string PythonInterpreter::runtimeInfo() +{ + std::stringstream result; + + // Runtime environment + result << std::string(60, '=') << "\n"; + /* DEBUG + + result << "PATH: " << BaseUtils::System::getenv("PATH") << "\n"; + result << "PYTHONPATH: " << BaseUtils::System::getenv("PYTHONPATH") << "\n"; + result << "PYTHONHOME: " << BaseUtils::System::getenv("PYTHONHOME") << "\n"; + + */ + + // Embedded Python details + result << "Py_GetProgramName(): " << toString(Py_GetProgramName()) << "\n"; + result << "Py_GetProgramFullPath(): " << toString(Py_GetProgramFullPath()) + << "\n"; + result << "Py_GetPath(): " << toString(Py_GetPath()) << "\n"; + result << "Py_GetPythonHome(): " << toString(Py_GetPythonHome()) << "\n"; + + // Runtime Python's sys.path + PyObject* sysPath = PySys_GetObject((char*)"path"); + Result<std::vector<std::string>> content {toVectorString(sysPath)}; + if(content.valid()) { + result << "sys.path: "; + for (auto s : content.val) + result << s << ","; + result << "\n"; + } + + return result.str(); +} + +// Attempt to retrieve Python stack trace +// Ref: <https://stackoverflow.com/q/1796510> +std::string PythonInterpreter::stackTrace() +{ + std::stringstream result; + + if (PyErr_Occurred()) { + PyObject *ptype, *pvalue, *ptraceback, *pystr; + + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + pystr = PyObject_Str(pvalue); + if (char* str = PyBytes_AsString(pystr)) + result << std::string(str) << "\n"; + Py_DecRef(pystr); + + PyObject* module_name = PyUnicode_FromString("traceback"); + PyObject* py_traceback_module = PyImport_Import(module_name); + Py_DecRef(module_name); + + if (py_traceback_module) { + result << "\n"; + PyObject* pyth_func = PyObject_GetAttrString(py_traceback_module, "format_exception"); + if (pyth_func && PyCallable_Check(pyth_func)) { + PyObject* pyth_val = + PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL); + Py_DecRef(pyth_func); + if (pyth_val) { + pystr = PyObject_Str(pyth_val); + if (char* str = PyBytes_AsString(pystr)) + result << std::string(str); + Py_DecRef(pystr); + Py_DecRef(pyth_val); + } + } + result << "\n"; + } + + Py_DecRef(py_traceback_module); + } + + result << "\n"; + result << PythonInterpreter::runtimeInfo(); + result << "\n"; + + return result.str(); +} + + +std::string PythonInterpreter::errorDescription(const std::string& title) +{ + std::stringstream msg; + msg << title << "\n"; + msg << PythonInterpreter::stackTrace() << "\n"; + return msg.str(); +} + +//---------------------------------------- + +Status PythonInterpreter::Numpy::initialize() { + + // initialize Python C API, if needed + Status py_status{PythonInterpreter::initialize()}; + if(py_status.isError()) + return py_status; + + const int res {_init_numpy()}; + + switch(res) + { + case 1: + return {Status::Type::Info, "PythonInterpreter: Initialized Numpy"}; + case 2: + return {Status::Type::Warning, "PythonInterpreter: Numpy already initialized"}; + default: + return {Status::Type::Error, "PythonInterpreter: Cannot initialize Numpy"}; + } +} + + +bool PythonInterpreter::Numpy::isInitialized() +{ + return static_cast<bool>(PyArray_API); +} + +//---------------------------------------- + +Result<NpArrayDescr> PythonInterpreter::Numpy::arrayDescriptor( + PyObject* pyobject_ptr) +{ + if(!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: " + "Invalid Numpy array")}; + } + + PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; + PyArray_Descr* npArray_descr = PyArray_DTYPE(npArray_ptr); + const int npArray_type = PyArray_TYPE(npArray_ptr); + const int npArray_ndim = PyArray_NDIM(npArray_ptr); + npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); + const int npArray_flags = PyArray_FLAGS(npArray_ptr); + // character code indicating the data type + const char npArray_dtype = npArray_descr->type; + + + NpArrayDescr np_descr_out; + np_descr_out.n_dims = npArray_ndim; + if (np_descr_out.n_dims > 0) { + np_descr_out.dims.resize(npArray_ndim); + for(npy_intp i_d = 0; i_d < npArray_ndim; ++i_d) + np_descr_out.dims[i_d] = npArray_dims[i_d]; + } + + np_descr_out.dtype = npArray_dtype; + + if(PyArray_IS_C_CONTIGUOUS(npArray_ptr)) + np_descr_out.C_contiguous = true; + + if(PyArray_IS_F_CONTIGUOUS(npArray_ptr)) + np_descr_out.F_contiguous = true; + + if(PyArray_ISBEHAVED_RO(npArray_ptr)) + np_descr_out.wellbehaved = true; + + if(npArray_flags & NPY_ARRAY_OWNDATA) + np_descr_out.owns_data = true; + + return {std::move(np_descr_out)}; +} + + +Result<std::vector<double>> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) +{ + if(!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: Cannot convert an invalid " + "Numpy array to a C-Array")}; + } + + PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; + PyArray_Descr* npArray_descr = PyArray_DTYPE(npArray_ptr); + const int npArray_type = PyArray_TYPE(npArray_ptr); + const int npArray_ndim = PyArray_NDIM(npArray_ptr); + npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); + const std::size_t npArray_size = static_cast<std::size_t> + (PyArray_SIZE(npArray_ptr)); + const int npArray_flags = PyArray_FLAGS(npArray_ptr); + // character code indicating the data type + const char npArray_dtype = npArray_descr->type; + + // Numpy array type must be 2d + if(npArray_ndim != 2) { + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::Numpy: " + << "Expected a Numpy 2d-array " + << "(given number of dimensions is " << npArray_ndim << ")"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // Numpy array type must be numeric and real (eligible for casting to double) + if(!PyDataType_ISNUMBER(npArray_descr) || PyDataType_ISCOMPLEX(npArray_descr)) { + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::Numpy: " + << "Expected a Numpy array of numeric type and real " + << "(given type '" << npArray_dtype << "')"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + std::vector<double> data(npArray_size); + + const npy_intp rowIdxMax = npArray_dims[0], colIdxMax = npArray_dims[1]; + + // branch on type + switch(npArray_type) + { + // float + case NPY_DOUBLE: + _realArray2DToDouble<npy_double>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + case NPY_FLOAT: + _realArray2DToDouble<npy_float>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + // signed int + case NPY_INT8: + _realArray2DToDouble<npy_int8>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + case NPY_INT16: + _realArray2DToDouble<npy_int16>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + case NPY_INT32: + _realArray2DToDouble<npy_int32>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + case NPY_LONG: + _realArray2DToDouble<npy_long>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + // unsigned int + case NPY_UINT8: + _realArray2DToDouble<npy_uint8>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + case NPY_UINT16: + _realArray2DToDouble<npy_uint16>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + case NPY_UINT32: + _realArray2DToDouble<npy_uint32>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + case NPY_ULONG: + _realArray2DToDouble<npy_ulong>(npArray_ptr, rowIdxMax, colIdxMax, data); + break; + default: + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::Numpy: " + << "Conversion of Numpy array of dtype '" << npArray_dtype << "' " + << "to vector<double> is not implemented"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::Numpy: " + << "Numpy 2d-array of dtype '" << npArray_dtype << "' " + << "and shape (" << npArray_dims[0] << "," << npArray_dims[1] << ") " + << "converted to vector<double> with size " << data.size(); + + return {std::move(data), Status(Status::Type::Info, msg_ss.str())}; +} + + +PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC( + double* const c_array, const np_size_t dims[2]) +{ + if (!c_array) { + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a null data pointer")}; + } + + const np_size_t size = dims[0] * dims[1]; + if (size < 1) { + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a data with size = 0")}; + } + + npy_intp npDims[2] = {dims[0], dims[1]}; + + // create stand-alone Numpy array (float64) + PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 2, + npDims, NPY_DOUBLE); + if (!npArray_ptr) { + checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::Numpy: " + << "Cannot create a Numpy 1D-array from the " + << "given data (size = " << size << ")"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // obtain pointer to data buffer of Numpy array + double* array_buffer = static_cast<double*>( + PyArray_DATA((PyArrayObject*)(npArray_ptr))); + + for (np_size_t index = 0; index < size; ++index) + { + *array_buffer = c_array[index]; + ++array_buffer; + } + + // returns a _new_ reference; ie. caller is responsible for the ref-count + return {npArray_ptr}; +} + + +PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC( + double* const c_array, const np_size_t size) +{ + if (!c_array) { + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a null data pointer")}; + } + + if (size < 1) { + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a data with size = 0")}; + } + + npy_intp npDims[1] = {size}; + + // create stand-alone Numpy array (float64) + PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 1, + npDims, NPY_DOUBLE); + if (!npArray_ptr) { + checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::Numpy: " + << "Cannot create a Numpy 1D-array from the " + << "given data (size = " << size << ")"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // obtain pointer to data buffer of Numpy array + double* array_buffer = static_cast<double*>( + PyArray_DATA((PyArrayObject*)(npArray_ptr))); + + for (np_size_t index = 0; index < size; ++index) + { + *array_buffer = c_array[index]; + ++array_buffer; + } + + // returns a _new_ reference; ie. caller is responsible for the ref-count + return {npArray_ptr}; +} + + +PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D( + double* const c_array, const np_size_t dims[2]) +{ + if (!c_array) { + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: " + "Cannot create a Numpy 2D-array from a null data pointer")}; + } + + const np_size_t size = dims[0] * dims[1]; + if (size < 1) { + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: " + "Cannot create a Numpy 2D-array from a data with size = 0")}; + } + + npy_intp npDims[2] = {dims[0], dims[1]}; + + // convert the contiguous C-array to Numpy array + PyObject* npArray_ptr = PyArray_SimpleNewFromData( + /* n_dims */ 2, npDims, NPY_DOUBLE, reinterpret_cast<void*>(c_array)); + + if(!npArray_ptr || !PyArray_Check(npArray_ptr)) { + PythonInterpreter::checkError(); + return {Status(Status::Type::Error, + "PythonInterpreter::Numpy: Cannot convert the given " + "C-Array to a Numpy 2D-array")}; + } + + // returns a _new_ reference; ie. caller is responsible for the ref-count + return {npArray_ptr}; +} + + +PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensions) +{ + // descriptors for the array dimensions + const std::size_t n_dims = dimensions.size(); + if (n_dims < 1) { + return {Status(Status::Type::Error, + "Cannot make a Numpy with the given number of dimensions; " + "number of dimensions must be >= 1")}; + } + + for (std::size_t d = 0; d < n_dims; ++d) { + if (dimensions[d] < 2) { + return {Status(Status::Type::Error, + "Cannot make a Numpy with the given dimensions; " + "dimensions must be >= 2")}; + } + } + + npy_int ndim_numpy = static_cast<npy_int>(n_dims); + npy_intp* ndimsizes_numpy = new npy_intp[n_dims]; + for (std::size_t d = 0; d < n_dims; ++d) + ndimsizes_numpy[d] = dimensions[d]; + + // creating standalone numpy array + PyObject* npyArray_ptr = PyArray_SimpleNew( + ndim_numpy, ndimsizes_numpy, NPY_DOUBLE); + delete[] ndimsizes_numpy; + + if (!npyArray_ptr) { + checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::Numpy: " + << "Cannot create a Numpy " << n_dims << "D-array " + << "from the given data"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + return {npyArray_ptr}; +} + + +Result<double*> PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) +{ + + PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; + + // get the pointer to data buffer of the Numpy array + double* data_ptr = static_cast<double*>( + PyArray_DATA((PyArrayObject*)npArray_ptr)); + + if (!data_ptr) { + checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::Numpy: " + << "Numpy array has invalid data pointer"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + return {std::move(data_ptr)}; +} + +//---------------------------------------- + +PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) +{ + PythonInterpreter::addPythonPath(path); + +#ifndef _WIN32 + // Stores signal handler before numpy's mess it up. + // This is to make ctrl-c working from terminal. + + PyOS_sighandler_t sighandler; + sighandler = PyOS_getsig(SIGINT); +#endif + + PyObject* ba_pymodule = PyImport_ImportModule("bornagain"); + + if (!ba_pymodule || !PyModule_Check(ba_pymodule)) { + checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter: Cannot load 'bornagain' Python module " + << "(given path = '" << path << "')"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // restores single handler to make ctr-c alive. +#ifndef _WIN32 + PyOS_setsig(SIGINT, sighandler); +#endif + + return {ba_pymodule}; +} + + +PyObjectPtr PythonInterpreter::BornAgain::importScript( + const std::string& script, const std::string& path) +{ + PyObjectPtr ba_pymodule {PythonInterpreter::BornAgain::import(path)}; + // TODO: Check ba module + + PyObject* pCompiledFn = Py_CompileString(script.c_str(), "", Py_file_input); + if (!pCompiledFn) { + ba_pymodule.discard(); + return {Status(Status::Type::Error, + PythonInterpreter::errorDescription( + "Cannot compile Python snippet"))}; + } + + // create a module + PyObject* tmpModule = PyImport_ExecCodeModule((char*)"tmpModule", pCompiledFn); + if (!tmpModule) { + Py_DecRef(pCompiledFn); + ba_pymodule.discard(); + return {Status(Status::Type::Error, + PythonInterpreter::errorDescription( + "Cannot execute Python snippet"))}; + } + + return {tmpModule}; +} + + +Result<std::vector<std::string>> PythonInterpreter::BornAgain::listOfFunctions( + const std::string& script, const std::string& path) +{ + PyObjectPtr tmpModule {PythonInterpreter::BornAgain::importScript(script, path)}; + + PyObject* pDict = PyModule_GetDict(tmpModule.ptr); + if (!pDict) { + return {Status(Status::Type::Error, "PythonInterpreter::BornAgain: " + "Cannot obtain the dictionary from the script module")}; + } + + PyObject *key, *value; + Py_ssize_t pos = 0; + std::vector<std::string> fn_names; + while (PyDict_Next(pDict, &pos, &key, &value)) { + if (PyCallable_Check(value)) { + std::string func_name {PythonInterpreter::pyStrtoString(key)}; + if (func_name.find("__") == std::string::npos) + fn_names.push_back(func_name); + } + } + + Py_DecRef(pDict); + + return {std::move(fn_names)}; +} diff --git a/PyTools/Embed/PythonInterpreter.h b/PyTools/Embed/PythonInterpreter.h new file mode 100644 index 00000000000..db62584b2b4 --- /dev/null +++ b/PyTools/Embed/PythonInterpreter.h @@ -0,0 +1,103 @@ +#ifndef PYTHONINTRP_H +#define PYTHONINTRP_H + +#include <vector> +#include <string> +#include "PyObjectPtr.h" + +class MultiLayer; + +// Embedded Python interpreter +namespace PythonInterpreter { + +// Python stable ABI +Status initialize(); + +// Python stable ABI +bool isInitialized(); + +// Python stable ABI +Status finalize(); + +// Python stable ABI +Status checkError(); + +// Python stable ABI +void addPythonPath(const std::string& path); + +// Python stable ABI +int setPythonPath(const std::string& path); + +// Python stable ABI +PyObjectPtr import(const std::string& pymodule_name, + const std::string& path = ""); +// Python stable ABI +void DecRef(PyObject* py_object); + +//! Converts String PyObject into string, if possible, or throws exception +std::string pyStrtoString(PyObject* obj); + +//! Returns multi-line string representing PATH, PYTHONPATH, sys.path and other info. +std::string runtimeInfo(); + +//! Returns string representing python stack trace. +std::string stackTrace(); + +//! Returns the full error description via Python stack trace +std::string errorDescription(const std::string& title = ""); + +//-- Numpy-related functionality +namespace Numpy { + +// initialize Numpy +Status initialize(); + +// Python stable ABI +bool isInitialized(); + +//! Obtains the descriptor of a Numpy array +Result<NpArrayDescr> arrayDescriptor(PyObject* pyobject_ptr); + +//! Creates an empty Numpy array of type `double` with the given dimensions +PyObjectPtr arrayND(std::vector<std::size_t>& dimensions); + +//! Returns the pointer to the data buffer of a Numpy array of type `double` +Result<double*> getDataPtr(PyObject* pyobject_ptr); + +//! Creates a Numpy 1D-array from a contiguous C-array of type `double`. +// Data will be copied. +PyObjectPtr createArray1DfromC(double* const c_array, const np_size_t size); + +// !Creates a Numpy 2D-array from a contiguous C-array of type `double`. +// Data will be copied. +PyObjectPtr createArray2DfromC(double* const c_array, const np_size_t dims[2]); + +// !Creates a `vector<double>` from a Numpy 2D-array (data will be copied). +Result<std::vector<double>> createVectorFromArray2D(PyObject* pyobject_ptr); + +// !Creates a 2D Numpy array from a contiguous C-array of type `double`. +// The data is _not_ copied, and the Numpy array will _not_ own its data. +PyObjectPtr CArrayAsNpy2D(double* const c_array, const np_size_t dims[2]); + +} // namespace Numpy + +//-- BornAgain-related functionality +namespace BornAgain { +//! Imports BornAgain from given location. If path is empty, tries to rely on PYTHONPATH. +PyObjectPtr import(const std::string& path = ""); + +// import a 'BornAgain' Python script +PyObjectPtr importScript(const std::string& script, + const std::string& path); + +//! Returns list of functions defined in the script. +//! @param script: Python script +//! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH +Result<std::vector<std::string>> listOfFunctions( + const std::string& script, const std::string& path); + +} // namespace BornAgain + +} // namespace PythonInterpreter + +#endif // PYTHONINTRP_H -- GitLab From 0e5ddf8bdad1b25ad98842daa7929d388f0b9cd3 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 7 Feb 2022 13:34:45 +0100 Subject: [PATCH 06/85] PythonInterpreter: Add Fabio functionality --- PyTools/Embed/PythonInterpreter.cpp | 64 +++++++++++++++++++++++++++++ PyTools/Embed/PythonInterpreter.h | 10 +++++ 2 files changed, 74 insertions(+) diff --git a/PyTools/Embed/PythonInterpreter.cpp b/PyTools/Embed/PythonInterpreter.cpp index bae6070c731..3855791516d 100644 --- a/PyTools/Embed/PythonInterpreter.cpp +++ b/PyTools/Embed/PythonInterpreter.cpp @@ -951,3 +951,67 @@ Result<std::vector<std::string>> PythonInterpreter::BornAgain::listOfFunctions( return {std::move(fn_names)}; } + +//---------------------------------------- + +PyObjectPtr PythonInterpreter::Fabio::import() +{ + return {PythonInterpreter::import("fabio")}; +} + + +PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, + PyObjectPtr& fabio_module) +{ + // load an image via calling `fabio.load` function which takes a + // filename (Python string) and returns a Numpy array. + + if (!fabio_module.valid() || !PyModule_Check(fabio_module.ptr)) { + return {Status(Status::Type::Error, + "PythonInterpreter.fabio: Invalid Python module " + "(expected 'fabio' module)")}; + } + + PyObject* pFunc = PyObject_GetAttrString(fabio_module.ptr, (char*)"open"); + if (!pFunc || !PyCallable_Check(pFunc)) { + PythonInterpreter::checkError(); + return {Status(Status::Type::Error, + "PythonInterpreter.fabio: The function 'fabio.open' is not callable")}; + } + + // convert the filename to a Python unicode string + PyObject* pFilename = PyUnicode_FromString(filename.c_str()); + if (!pFilename) { + PythonInterpreter::checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter.fabio: Filename '" << filename << "' " + << "cannot be converted to Python unicode string"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // get the result of the call `fabio.open(<filename>)` + PyObject* pResult_open = PyObject_CallFunctionObjArgs(pFunc, pFilename, NULL); + Py_DecRef(pFunc); + if (!pResult_open) { + PythonInterpreter::checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter.fabio: Invalid return value from " + << "calling the function 'fabio.open(\"" << filename << "\")'"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // get `result.data` (must be a Numpy array) + PyObject* npyArray_ptr = PyObject_GetAttrString(pResult_open, (char*)"data"); + Py_DecRef(pResult_open); + if (!npyArray_ptr || !PyArray_Check(npyArray_ptr)) { + PythonInterpreter::checkError(); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter.fabio: Invalid return value from " + << "calling the function 'fabio.open(\"" << filename << "\")' " + << "(expected a Numpy array)"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // returns a _new_ reference; ie. caller is responsible for the ref-count + return {npyArray_ptr}; +} diff --git a/PyTools/Embed/PythonInterpreter.h b/PyTools/Embed/PythonInterpreter.h index db62584b2b4..26219081b53 100644 --- a/PyTools/Embed/PythonInterpreter.h +++ b/PyTools/Embed/PythonInterpreter.h @@ -98,6 +98,16 @@ Result<std::vector<std::string>> listOfFunctions( } // namespace BornAgain +//-- Fabio-related functionality +namespace Fabio { +// import 'fabio' Python module +PyObjectPtr import(); +// call 'fabio.open' on a given file (needs Numpy) +PyObjectPtr open(const std::string& filename, + PyObjectPtr& fabio_module); + +} // namespace Fabio + } // namespace PythonInterpreter #endif // PYTHONINTRP_H -- GitLab From 1cf3ecfcd600d07c5f684ef175762223a0dc9bf5 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Thu, 10 Feb 2022 13:11:14 +0100 Subject: [PATCH 07/85] PythonInterpreter: Remove unused functions or variables --- PyTools/Embed/PythonInterpreter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyTools/Embed/PythonInterpreter.cpp b/PyTools/Embed/PythonInterpreter.cpp index 3855791516d..cc3807fb94f 100644 --- a/PyTools/Embed/PythonInterpreter.cpp +++ b/PyTools/Embed/PythonInterpreter.cpp @@ -551,7 +551,7 @@ Result<NpArrayDescr> PythonInterpreter::Numpy::arrayDescriptor( PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; PyArray_Descr* npArray_descr = PyArray_DTYPE(npArray_ptr); - const int npArray_type = PyArray_TYPE(npArray_ptr); + // const int npArray_type = PyArray_TYPE(npArray_ptr); const int npArray_ndim = PyArray_NDIM(npArray_ptr); npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); const int npArray_flags = PyArray_FLAGS(npArray_ptr); -- GitLab From e74df853fc676279b40bb6a3e8c76df0545cd8a9 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Sat, 5 Feb 2022 17:29:48 +0100 Subject: [PATCH 08/85] Amend PythonInterpreter for Windows compiler MSVC 19 The change prevents the following errors from Windows compiler MSVC 19 on x64 platform: ``` error C2196: case value 'NPY_LONG' already used error C2196: case value 'NPY_ULONG' already used ``` --- PyTools/Embed/PythonInterpreter.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/PyTools/Embed/PythonInterpreter.cpp b/PyTools/Embed/PythonInterpreter.cpp index cc3807fb94f..50df04d84fc 100644 --- a/PyTools/Embed/PythonInterpreter.cpp +++ b/PyTools/Embed/PythonInterpreter.cpp @@ -646,9 +646,16 @@ Result<std::vector<double>> PythonInterpreter::Numpy::createVectorFromArray2D(Py case NPY_INT32: _realArray2DToDouble<npy_int32>(npArray_ptr, rowIdxMax, colIdxMax, data); break; + +// prevent the error from Windows compiler MSVC 19: +// `error C2196: case value 'NPY_LONG' already used` +#ifndef _WIN32 + case NPY_LONG: _realArray2DToDouble<npy_long>(npArray_ptr, rowIdxMax, colIdxMax, data); break; +#endif // _WIN32 + // unsigned int case NPY_UINT8: _realArray2DToDouble<npy_uint8>(npArray_ptr, rowIdxMax, colIdxMax, data); @@ -659,9 +666,15 @@ Result<std::vector<double>> PythonInterpreter::Numpy::createVectorFromArray2D(Py case NPY_UINT32: _realArray2DToDouble<npy_uint32>(npArray_ptr, rowIdxMax, colIdxMax, data); break; + +// prevent the error from Windows compiler MSVC 19: +// `error C2196: case value 'NPY_ULONG' already used` +#ifndef _WIN32 case NPY_ULONG: _realArray2DToDouble<npy_ulong>(npArray_ptr, rowIdxMax, colIdxMax, data); break; +#endif // _WIN32 + default: std::stringstream msg_ss; msg_ss << "PythonInterpreter::Numpy: " -- GitLab From 786f8c2f852ce44422ae477f673710d3f0b4838b Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Wed, 2 Feb 2022 11:05:04 +0100 Subject: [PATCH 09/85] Add PyTools/CMakeLists.txt --- PyTools/CMakeLists.txt | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 PyTools/CMakeLists.txt diff --git a/PyTools/CMakeLists.txt b/PyTools/CMakeLists.txt new file mode 100644 index 00000000000..18f6637af92 --- /dev/null +++ b/PyTools/CMakeLists.txt @@ -0,0 +1,27 @@ +############################################################################ +# CMakeLists.txt file for building libBornAgainPyCore +############################################################################ + +# NOTE: The module is directly dependent on Python and Numpy C-API +if(NOT BORNAGAIN_PYTHON) + return() +endif() + +set(component PyCore) +set(name PyCore) +set(lib BornAgain${name}) + +# --- source and include files --- +# Python-dependent source files +file(GLOB source_files */*.cpp) +file(GLOB include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) + +target_include_directories(${lib} + PRIVATE + ${Python3_INCLUDES} +) + +target_link_libraries(${lib} + PUBLIC + ${Python3_LIBRARIES} +) -- GitLab From 8e73fd755d9321303205a21471aca01d018a2ad0 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Thu, 3 Feb 2022 12:02:22 +0100 Subject: [PATCH 10/85] Add PyTools/Sample/CMakeLists.txt --- PyTools/Sample/CMakeLists.txt | 64 +++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 PyTools/Sample/CMakeLists.txt diff --git a/PyTools/Sample/CMakeLists.txt b/PyTools/Sample/CMakeLists.txt new file mode 100644 index 00000000000..b8adcb949f5 --- /dev/null +++ b/PyTools/Sample/CMakeLists.txt @@ -0,0 +1,64 @@ +############################################################################ +# CMakeLists.txt file for building libBornAgainPyTools +############################################################################ + +# NOTE: The module is directly dependent on Python and Numpy C-API +if(NOT BORNAGAIN_PYTHON) + return() +endif() + +set(name PyImportMultiLayer) +set(basename BornAgain${name}) + +# --- source and include files --- +# Python-dependent source files +file(GLOB ${basename}_SRC_PY */*.cpp) +file(GLOB ${basename}_INCLUDES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) + +# --- compile properties --- +set(${basename}_INCLUDE_DIRS_PUBLIC + ${CMAKE_SOURCE_DIR} + ${LibHeinz_INCLUDE_DIR} + ) + +#-- build libraries for multiple Python versions +message(STATUS "Define ${basename} for multiple Python versions " + "(${BORNAGAIN_PYTHON_PACKAGE_VERSIONS})...") + +foreach(pyminversion ${BORNAGAIN_PYTHON_PACKAGE_MINOR_VERSIONS}) + if(NOT ${pyminversion} STREQUAL "<MAIN>") + set(pytag py3${pyminversion}) # eg. 'py38' + set(pysuffix _${pytag}) # eg. '_py38' + set(is_main NO) + else() + # the main version of the library has no Python tag + set(pytag "") + set(pysuffix "") + set(is_main YES) + endif() + + # Python-dependent objects + set(objlib_py ${basename}_objects_py${pysuffix}) + add_library(${objlib_py} OBJECT) + set_target_properties(${objlib_py} PROPERTIES + POSITION_INDEPENDENT_CODE ON + SOURCES "${${basename}_SRC_PY}" + ) + + target_compile_definitions(${objlib_py} PUBLIC BORNAGAIN_PYTHON) + + target_include_directories(${objlib_py} + PUBLIC + ${CMAKE_SOURCE_DIR} + ${LibHeinz_INCLUDE_DIR} + PRIVATE + "${Python3_INCLUDE_DIRS${pysuffix}}" + "${Python3_NumPy_INCLUDE_DIRS${pysuffix}}" + ) + + target_link_libraries(${objlib_py} + PUBLIC + "${Python3_LIBRARY_RELEASE${pysuffix}}" + "BornAgainPyTools${pysuffix}") + +endforeach() -- GitLab From d812604db4b04f172250eb756e9d68b177267ce3 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 1 Feb 2022 14:58:01 +0100 Subject: [PATCH 11/85] Add PyTools/Embed/Makefile [DEBUG] This is used only for development. --- PyTools/Embed/Makefile | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 PyTools/Embed/Makefile diff --git a/PyTools/Embed/Makefile b/PyTools/Embed/Makefile new file mode 100644 index 00000000000..57a593ed7eb --- /dev/null +++ b/PyTools/Embed/Makefile @@ -0,0 +1,28 @@ +.PHONY: all clean py + +all: py + + +CC=gcc +LINK=gcc +CXX=g++ + +CXX_INCLUDES=-I/usr/include/python3.9 -I$${HOME}/Projects/bornagain -I$${HOME}/.local/opt/libheinz/include +CXX_LINKS=-lpython3.9 + +BA_FLAGS=-DBORNAGAIN_PYTHON +CXX_CFLAGS=-Wall -Wextra -Wpedantic -fsanitize=undefined ${BA_FLAGS} ${CXX_INCLUDES} +CXX_LFLAGS= ${CXX_LINKS} # -Wl,-verbose +CPP_CFLAGS=-std=c++17 + +py: PythonInterpreter.cpp PythonInterpreter.h PyObjectPtr.cpp PyObjectDecl.h + @echo "Making an embedded Python interpreter..." + ${CXX} -c -fPIC Result.cpp ${CPP_CFLAGS} ${CXX_CFLAGS} + ${CXX} -c -fPIC PythonInterpreter.cpp PyObjectPtr.cpp ${CPP_CFLAGS} ${CXX_CFLAGS} + ${CXX} -shared -o libpythonembed.so PythonInterpreter.o PyObjectPtr.o Result.o ${CXX_CFLAGS} + ${CXX} -o testEmbeddedPython.exe test_embedded_python.cpp ${CXX_CFLAGS} ${CPP_CFLAGS} -L. -lpythonembed ${CXX_LFLAGS} '-Wl,--disable-new-dtags,-rpath,$$ORIGIN' + + +clean: + @echo "Cleaning all products..." + rm *.o *.out *.exe -- GitLab From 7a2d4ebd95bd29789a5ee05c1e7b95a1497ef6eb Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 1 Feb 2022 14:57:27 +0100 Subject: [PATCH 12/85] Sample/Multilayer/PyImport: Import a MultiLayer instance from a Python code --- .../Sample/Multilayer/ImportMultiLayer.cpp | 73 +++++++++++++++++++ PyTools/Sample/Multilayer/ImportMultiLayer.h | 23 ++++++ 2 files changed, 96 insertions(+) create mode 100644 PyTools/Sample/Multilayer/ImportMultiLayer.cpp create mode 100644 PyTools/Sample/Multilayer/ImportMultiLayer.h diff --git a/PyTools/Sample/Multilayer/ImportMultiLayer.cpp b/PyTools/Sample/Multilayer/ImportMultiLayer.cpp new file mode 100644 index 00000000000..53c7f15d5d2 --- /dev/null +++ b/PyTools/Sample/Multilayer/ImportMultiLayer.cpp @@ -0,0 +1,73 @@ +#ifdef BORNAGAIN_PYTHON + +#include "PyTools/Embed/PyCore.h" +#include "ImportMultiLayer.h" +#include "Sample/Multilayer/MultiLayer.h" +#include "PyTools/Embed/PythonInterpreter.h" // BornAgain::importScript +// SWIG runtime access for creating a MultiLayer instance from Python +#include "auto/Wrap/swig_runtime.h" + + +namespace PythonInterpreter +{ + +Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython( + const std::string& script, const std::string& functionName, + const std::string& path) +{ + PyObjectPtr tmpModule {PythonInterpreter::BornAgain::importScript(script, path)}; + + // locate the `get_simulation` function (it is an attribute of the module) + PyObject* pAddFn = PyObject_GetAttrString(tmpModule.ptr, functionName.c_str()); + if (!pAddFn) { + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::BornAgain: " + << "Cannot locate the compiled function '" << functionName << "'"; + return {Status(Status::Type::Error, msg_ss.str())}; + } + + // create a `MultiLayer` Python object via calling the function + PyObject* instance = PyObject_CallFunctionObjArgs(pAddFn, NULL); + + // clean up + Py_DecRef(pAddFn); + + if (!instance) { + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::BornAgain: " + << "Cannot call the function '" << functionName << "'"; + return {Status(Status::Type::Error, + PythonInterpreter::errorDescription(msg_ss.str()))}; + } + + // Construct a C++ object from the Python object + /* NOTE: + This conversion requires a SWIG-produced Python API. + */ + + void* argp1 = 0; + swig_type_info* pTypeInfo = SWIG_TypeQuery("MultiLayer *"); + + const int res = SWIG_ConvertPtr(instance, &argp1, pTypeInfo, 0); + if (!SWIG_IsOK(res)) { + Py_DecRef(instance); + std::stringstream msg_ss; + msg_ss << "PythonInterpreter::BornAgain: " + << "SWIG failed to extract a 'MultiLayer' instance " + << "via calling the function '" << functionName << "'"; + return {Status(Status::Type::Error, + PythonInterpreter::errorDescription(msg_ss.str()))}; + } + + MultiLayer* multilayer = reinterpret_cast<MultiLayer*>(argp1); + std::unique_ptr<MultiLayer> result_ptr(multilayer->clone()); + + Py_DecRef(instance); + + // TODO: check if it is possible to move a unique_ptr + return {std::move(result_ptr)}; +} + +} // namespace PythonInterpreter + +#endif // BORNAGAIN_PYTHON diff --git a/PyTools/Sample/Multilayer/ImportMultiLayer.h b/PyTools/Sample/Multilayer/ImportMultiLayer.h new file mode 100644 index 00000000000..f8731529504 --- /dev/null +++ b/PyTools/Sample/Multilayer/ImportMultiLayer.h @@ -0,0 +1,23 @@ +#ifndef SWIG + +#include <string> +#include <memory> // unique_ptr +#include "PyTools/Embed/PyObjectDecl.h" +#include "PyTools/Embed/Result.h" + +class MultiLayer; + +namespace PythonInterpreter +{ + +//! Creates a multi layer by running python code in embedded interpreter. +//! @param script: Python script +//! @param functionName: A function name in this script which produces a MultiLayer +//! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH +Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython( + const std::string& script, const std::string& functionName, + const std::string& path = ""); + +} // namespace PythonInterpreter + +#endif // SWIG -- GitLab From c4515e4f478d9c3b1c3c554ae6547c578ad2d7d0 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Thu, 3 Feb 2022 11:24:10 +0100 Subject: [PATCH 13/85] GUI/View/Project/PyImportAssistant: Use PythonInterpreter functionality Use PythonInterpreter functionality instead of using Python C-API directly. --- GUI/View/Project/PyImportAssistant.cpp | 42 ++++++++++++++------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index 58459747b16..97f20520155 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -30,7 +30,8 @@ #include "GUI/View/Project/ProjectManager.h" #include "GUI/View/Tool/Globals.h" #include "Sample/Multilayer/MultiLayer.h" -#include "Sample/Multilayer/PyImport.h" +#include "PyTools/Embed/PythonInterpreter.h" // listOfFunctions +#include "PyTools/Sample/Multilayer/ImportMultiLayer.h" // createMultiLayerFromPython #include <QApplication> #include <QFileDialog> #include <QTextStream> @@ -101,21 +102,23 @@ QString readFile(const QString& fileName) std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QString& funcName) { - std::unique_ptr<MultiLayer> result; - QApplication::setOverrideCursor(Qt::WaitCursor); - try { - result = Py::Import::createFromPython(snippet.toStdString(), funcName.toStdString(), - bornagainDir()); - } catch (const std::exception& ex) { + + Result<std::unique_ptr<MultiLayer>> result{ + PythonInterpreter::createMultiLayerFromPython( + snippet.toStdString(), funcName.toStdString(), + bornagainDir())}; + + if (!result.valid()) { QApplication::restoreOverrideCursor(); QString message("Exception thrown while executing Python code to create sample.\n\n"); - QString details = QString::fromStdString(std::string(ex.what())); - DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, details).exec(); + QString details = QString::fromStdString(result.status.message); + DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, details).exec(); } + QApplication::restoreOverrideCursor(); - return result; + return std::move(result.val); } //! Lets user select a function name which generates a MultiLayer. @@ -148,22 +151,23 @@ QString selectPySampleFunction(const QStringList& funcNames) QString getPySampleFunctionName(const QString& snippet) { - QStringList funcList; - QApplication::setOverrideCursor(Qt::WaitCursor); - try { - auto funcs = Py::Import::listOfFunctions(snippet.toStdString(), bornagainDir()); - funcList = GUI::Util::String::fromStdStrings(funcs); - } catch (const std::exception& ex) { + + Result<std::vector<std::string>> funcs_res { + PythonInterpreter::BornAgain::listOfFunctions( + snippet.toStdString(), bornagainDir())}; + + if(!funcs_res.valid()) + { QApplication::restoreOverrideCursor(); QString message("Exception thrown while acquiring functions from Python code.\n\n"); - QString details = QString::fromStdString(std::string(ex.what())); + QString details = QString::fromStdString(funcs_res.status.message); DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, details).exec(); - return ""; } - QApplication::restoreOverrideCursor(); + QApplication::restoreOverrideCursor(); + QStringList funcList {GUI::Util::String::fromStdStrings(funcs_res.val)}; return selectPySampleFunction(funcList); } -- GitLab From b9d0f3884174a1042ff45ebd889252d7b3f042b3 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 21 Apr 2023 16:50:11 +0200 Subject: [PATCH 14/85] Adapt GUI/CMakeLists.txt Also minor fix to compile flags. --- GUI/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/GUI/CMakeLists.txt b/GUI/CMakeLists.txt index 8f611c4327e..5c5c40fc54a 100644 --- a/GUI/CMakeLists.txt +++ b/GUI/CMakeLists.txt @@ -43,10 +43,15 @@ add_library(${lib} SHARED ${source_files} ${RC_SRCS} ${include_files} ${form_fil # switch OFF Qt debug output in any configuration except DEBUG target_compile_definitions(${lib} PUBLIC $<$<NOT:$<CONFIG:Debug>>:QT_NO_DEBUG_OUTPUT>) +if(BORNAGAIN_PYTHON) + target_compile_definitions(${lib} PRIVATE -DBORNAGAIN_PYTHON) +endif() + # --- external dependences --------- target_link_libraries(${lib} PUBLIC + BornAgainPyCore BornAgainSim BornAgainImg3D Qt6::Widgets -- GitLab From 601f9b7d7db533b0d1c19bede710165ade384e7b Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 17 Apr 2023 16:11:24 +0200 Subject: [PATCH 15/85] Adapt Device/Data/Datafield module --- Device/Data/Datafield.cpp | 53 ++++++++++++++++++++++++--------------- Device/Data/Datafield.h | 1 - 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/Device/Data/Datafield.cpp b/Device/Data/Datafield.cpp index aa03fee453c..af0c47bde4a 100644 --- a/Device/Data/Datafield.cpp +++ b/Device/Data/Datafield.cpp @@ -206,8 +206,17 @@ Datafield* Datafield::crop(double xmin, double xmax) const #ifdef BORNAGAIN_PYTHON -PyObject* Datafield::npArray() const +#include "PyTools/Embed/PyObjectDecl.h" +#include "PyTools/Embed/PythonInterpreter.h" // Numpy::arrayND, Numpy::getDataPtr + +template <> +PyObject* OutputData<double>::getArray() const { + // TODO: Thoroughly check this function regarding index manipulations + + Status init{PythonInterpreter::Numpy::initialize()}; + if (init.isError()) + return nullptr; std::vector<size_t> dimensions; for (size_t i = 0; i < rank(); i++) dimensions.push_back(axis(i).size()); @@ -217,38 +226,42 @@ PyObject* Datafield::npArray() const std::swap(dimensions[0], dimensions[1]); // creating ndarray objects describing size of dimensions - npy_int ndim_numpy = (int)dimensions.size(); - auto* ndimsizes_numpy = new npy_intp[dimensions.size()]; - for (size_t i = 0; i < dimensions.size(); i++) - ndimsizes_numpy[i] = dimensions[i]; + PyObjectPtr pyarray{PythonInterpreter::Numpy::arrayND(dimensions)}; + if (!pyarray.valid()) + return nullptr; - // creating standalone numpy array - PyObject* pyarray = PyArray_SimpleNew(ndim_numpy, ndimsizes_numpy, NPY_DOUBLE); - delete[] ndimsizes_numpy; - ASSERT(pyarray); + // get the pointer to the data buffer of the array (assumed to be C-contiguous) + Result<double*> data{PythonInterpreter::Numpy::getDataPtr(pyarray.ptr)}; - // getting pointer to data buffer of numpy array - double* array_buffer = (double*)PyArray_DATA((PyArrayObject*)pyarray); + if (!data.valid()) + return nullptr; + double* array_buffer = data.val; + const std::size_t idxMax = getAllocatedSize(); // filling numpy array with output_data if (rank() == 2) { - for (size_t i = 0; i < size(); ++i) { - std::vector<int> axes_indices = frame().allIndices(i); - size_t offset = - axes_indices[0] + axis(0).size() * (axis(1).size() - 1 - axes_indices[1]); - array_buffer[offset] = (*this)[i]; + for (size_t idx = 0; idx < idxMax; ++idx) { + const std::vector<int> axes_indices = getAxesBinIndices(idx); + const std::size_t ax0_size = m_axes[0]->size(), ax1_size = m_axes[1]->size(), + offset = + axes_indices[0] + ax0_size * (ax1_size - 1 - axes_indices[1]); + array_buffer[offset] = (*this)[idx]; + } + } else if (rank() == 1) { + for (size_t idx = 0; idx < idxMax; ++idx) { + *array_buffer = (*this)[idx]; + ++array_buffer; } } else { - for (size_t i = 0; i < size(); ++i) - *array_buffer++ = (*this)[i]; + return nullptr; } - return pyarray; + // returns a _new_ reference; ie. caller is responsible for the ref-count + return pyarray.release(); } #endif // BORNAGAIN_PYTHON - Datafield* Datafield::xProjection() const { return create_xProjection(0, static_cast<int>(xAxis().size()) - 1); diff --git a/Device/Data/Datafield.h b/Device/Data/Datafield.h index a468739a3c1..877fa8df262 100644 --- a/Device/Data/Datafield.h +++ b/Device/Data/Datafield.h @@ -15,7 +15,6 @@ #ifndef BORNAGAIN_DEVICE_DATA_DATAFIELD_H #define BORNAGAIN_DEVICE_DATA_DATAFIELD_H -#include "Base/Py/PyObject.h" #include <memory> #include <vector> -- GitLab From 4fa7b122fde9d675782879fc49190ceabab7eca4 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 17 Apr 2023 17:05:48 +0200 Subject: [PATCH 16/85] Rename 'PyTools' to 'PyCore' --- Device/Data/Datafield.cpp | 4 ++-- GUI/View/Project/PyImportAssistant.cpp | 4 ++-- {PyTools => PyCore}/CMakeLists.txt | 0 {PyTools => PyCore}/Embed/Makefile | 0 {PyTools => PyCore}/Embed/PyCore.h | 2 +- {PyTools => PyCore}/Embed/PyObjectDecl.h | 0 {PyTools => PyCore}/Embed/PyObjectPtr.cpp | 0 {PyTools => PyCore}/Embed/PyObjectPtr.h | 0 {PyTools => PyCore}/Embed/PythonInterpreter.cpp | 0 {PyTools => PyCore}/Embed/PythonInterpreter.h | 0 {PyTools => PyCore}/Embed/Result.cpp | 0 {PyTools => PyCore}/Embed/Result.h | 0 {PyTools => PyCore}/Sample/CMakeLists.txt | 0 {PyTools => PyCore}/Sample/Multilayer/ImportMultiLayer.cpp | 4 ++-- {PyTools => PyCore}/Sample/Multilayer/ImportMultiLayer.h | 4 ++-- 15 files changed, 9 insertions(+), 9 deletions(-) rename {PyTools => PyCore}/CMakeLists.txt (100%) rename {PyTools => PyCore}/Embed/Makefile (100%) rename {PyTools => PyCore}/Embed/PyCore.h (97%) rename {PyTools => PyCore}/Embed/PyObjectDecl.h (100%) rename {PyTools => PyCore}/Embed/PyObjectPtr.cpp (100%) rename {PyTools => PyCore}/Embed/PyObjectPtr.h (100%) rename {PyTools => PyCore}/Embed/PythonInterpreter.cpp (100%) rename {PyTools => PyCore}/Embed/PythonInterpreter.h (100%) rename {PyTools => PyCore}/Embed/Result.cpp (100%) rename {PyTools => PyCore}/Embed/Result.h (100%) rename {PyTools => PyCore}/Sample/CMakeLists.txt (100%) rename {PyTools => PyCore}/Sample/Multilayer/ImportMultiLayer.cpp (95%) rename {PyTools => PyCore}/Sample/Multilayer/ImportMultiLayer.h (89%) diff --git a/Device/Data/Datafield.cpp b/Device/Data/Datafield.cpp index af0c47bde4a..4309b20f374 100644 --- a/Device/Data/Datafield.cpp +++ b/Device/Data/Datafield.cpp @@ -206,8 +206,8 @@ Datafield* Datafield::crop(double xmin, double xmax) const #ifdef BORNAGAIN_PYTHON -#include "PyTools/Embed/PyObjectDecl.h" -#include "PyTools/Embed/PythonInterpreter.h" // Numpy::arrayND, Numpy::getDataPtr +#include "PyCore/Embed/PyObjectDecl.h" +#include "PyCore/Embed/PythonInterpreter.h" // Numpy::arrayND, Numpy::getDataPtr template <> PyObject* OutputData<double>::getArray() const diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index 97f20520155..d7bf56269ce 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -30,8 +30,8 @@ #include "GUI/View/Project/ProjectManager.h" #include "GUI/View/Tool/Globals.h" #include "Sample/Multilayer/MultiLayer.h" -#include "PyTools/Embed/PythonInterpreter.h" // listOfFunctions -#include "PyTools/Sample/Multilayer/ImportMultiLayer.h" // createMultiLayerFromPython +#include "PyCore/Embed/PythonInterpreter.h" // listOfFunctions +#include "PyCore/Sample/Multilayer/ImportMultiLayer.h" // createMultiLayerFromPython #include <QApplication> #include <QFileDialog> #include <QTextStream> diff --git a/PyTools/CMakeLists.txt b/PyCore/CMakeLists.txt similarity index 100% rename from PyTools/CMakeLists.txt rename to PyCore/CMakeLists.txt diff --git a/PyTools/Embed/Makefile b/PyCore/Embed/Makefile similarity index 100% rename from PyTools/Embed/Makefile rename to PyCore/Embed/Makefile diff --git a/PyTools/Embed/PyCore.h b/PyCore/Embed/PyCore.h similarity index 97% rename from PyTools/Embed/PyCore.h rename to PyCore/Embed/PyCore.h index 933c85ea937..10533b160a0 100644 --- a/PyTools/Embed/PyCore.h +++ b/PyCore/Embed/PyCore.h @@ -2,7 +2,7 @@ // // BornAgain: simulate and fit reflection and scattering // -//! @file PyTools/Embed/PyCore.h +//! @file PyCore/Embed/PyCore.h //! @brief Includes python header and takes care of warnings. //! //! @homepage http://www.bornagainproject.org diff --git a/PyTools/Embed/PyObjectDecl.h b/PyCore/Embed/PyObjectDecl.h similarity index 100% rename from PyTools/Embed/PyObjectDecl.h rename to PyCore/Embed/PyObjectDecl.h diff --git a/PyTools/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp similarity index 100% rename from PyTools/Embed/PyObjectPtr.cpp rename to PyCore/Embed/PyObjectPtr.cpp diff --git a/PyTools/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h similarity index 100% rename from PyTools/Embed/PyObjectPtr.h rename to PyCore/Embed/PyObjectPtr.h diff --git a/PyTools/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp similarity index 100% rename from PyTools/Embed/PythonInterpreter.cpp rename to PyCore/Embed/PythonInterpreter.cpp diff --git a/PyTools/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h similarity index 100% rename from PyTools/Embed/PythonInterpreter.h rename to PyCore/Embed/PythonInterpreter.h diff --git a/PyTools/Embed/Result.cpp b/PyCore/Embed/Result.cpp similarity index 100% rename from PyTools/Embed/Result.cpp rename to PyCore/Embed/Result.cpp diff --git a/PyTools/Embed/Result.h b/PyCore/Embed/Result.h similarity index 100% rename from PyTools/Embed/Result.h rename to PyCore/Embed/Result.h diff --git a/PyTools/Sample/CMakeLists.txt b/PyCore/Sample/CMakeLists.txt similarity index 100% rename from PyTools/Sample/CMakeLists.txt rename to PyCore/Sample/CMakeLists.txt diff --git a/PyTools/Sample/Multilayer/ImportMultiLayer.cpp b/PyCore/Sample/Multilayer/ImportMultiLayer.cpp similarity index 95% rename from PyTools/Sample/Multilayer/ImportMultiLayer.cpp rename to PyCore/Sample/Multilayer/ImportMultiLayer.cpp index 53c7f15d5d2..4da4157ac24 100644 --- a/PyTools/Sample/Multilayer/ImportMultiLayer.cpp +++ b/PyCore/Sample/Multilayer/ImportMultiLayer.cpp @@ -1,9 +1,9 @@ #ifdef BORNAGAIN_PYTHON -#include "PyTools/Embed/PyCore.h" +#include "PyCore/Embed/PyCore.h" #include "ImportMultiLayer.h" #include "Sample/Multilayer/MultiLayer.h" -#include "PyTools/Embed/PythonInterpreter.h" // BornAgain::importScript +#include "PyCore/Embed/PythonInterpreter.h" // BornAgain::importScript // SWIG runtime access for creating a MultiLayer instance from Python #include "auto/Wrap/swig_runtime.h" diff --git a/PyTools/Sample/Multilayer/ImportMultiLayer.h b/PyCore/Sample/Multilayer/ImportMultiLayer.h similarity index 89% rename from PyTools/Sample/Multilayer/ImportMultiLayer.h rename to PyCore/Sample/Multilayer/ImportMultiLayer.h index f8731529504..ac1714992ab 100644 --- a/PyTools/Sample/Multilayer/ImportMultiLayer.h +++ b/PyCore/Sample/Multilayer/ImportMultiLayer.h @@ -2,8 +2,8 @@ #include <string> #include <memory> // unique_ptr -#include "PyTools/Embed/PyObjectDecl.h" -#include "PyTools/Embed/Result.h" +#include "PyCore/Embed/PyObjectDecl.h" +#include "PyCore/Embed/Result.h" class MultiLayer; -- GitLab From 00e18178358aa5ba722f4e2ecc7b6b2474994b11 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 21 Apr 2023 14:19:22 +0200 Subject: [PATCH 17/85] rm unneeded PyCore/Sample/CMakeLists.txt --- PyCore/Sample/CMakeLists.txt | 64 ------------------------------------ 1 file changed, 64 deletions(-) delete mode 100644 PyCore/Sample/CMakeLists.txt diff --git a/PyCore/Sample/CMakeLists.txt b/PyCore/Sample/CMakeLists.txt deleted file mode 100644 index b8adcb949f5..00000000000 --- a/PyCore/Sample/CMakeLists.txt +++ /dev/null @@ -1,64 +0,0 @@ -############################################################################ -# CMakeLists.txt file for building libBornAgainPyTools -############################################################################ - -# NOTE: The module is directly dependent on Python and Numpy C-API -if(NOT BORNAGAIN_PYTHON) - return() -endif() - -set(name PyImportMultiLayer) -set(basename BornAgain${name}) - -# --- source and include files --- -# Python-dependent source files -file(GLOB ${basename}_SRC_PY */*.cpp) -file(GLOB ${basename}_INCLUDES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) - -# --- compile properties --- -set(${basename}_INCLUDE_DIRS_PUBLIC - ${CMAKE_SOURCE_DIR} - ${LibHeinz_INCLUDE_DIR} - ) - -#-- build libraries for multiple Python versions -message(STATUS "Define ${basename} for multiple Python versions " - "(${BORNAGAIN_PYTHON_PACKAGE_VERSIONS})...") - -foreach(pyminversion ${BORNAGAIN_PYTHON_PACKAGE_MINOR_VERSIONS}) - if(NOT ${pyminversion} STREQUAL "<MAIN>") - set(pytag py3${pyminversion}) # eg. 'py38' - set(pysuffix _${pytag}) # eg. '_py38' - set(is_main NO) - else() - # the main version of the library has no Python tag - set(pytag "") - set(pysuffix "") - set(is_main YES) - endif() - - # Python-dependent objects - set(objlib_py ${basename}_objects_py${pysuffix}) - add_library(${objlib_py} OBJECT) - set_target_properties(${objlib_py} PROPERTIES - POSITION_INDEPENDENT_CODE ON - SOURCES "${${basename}_SRC_PY}" - ) - - target_compile_definitions(${objlib_py} PUBLIC BORNAGAIN_PYTHON) - - target_include_directories(${objlib_py} - PUBLIC - ${CMAKE_SOURCE_DIR} - ${LibHeinz_INCLUDE_DIR} - PRIVATE - "${Python3_INCLUDE_DIRS${pysuffix}}" - "${Python3_NumPy_INCLUDE_DIRS${pysuffix}}" - ) - - target_link_libraries(${objlib_py} - PUBLIC - "${Python3_LIBRARY_RELEASE${pysuffix}}" - "BornAgainPyTools${pysuffix}") - -endforeach() -- GitLab From c091c9d7712923924f6880d350be326c9cc4fd5a Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 24 Apr 2023 11:42:47 +0200 Subject: [PATCH 18/85] CMakeLists.txt<root>: add PyCore module also minor improvements. --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e59c964668..b59ddabb0f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ set(CMAKE_CXX_STANDARD 20) ### Options # options that are on by default (switch off for accelerated builds of limited scope) -option(BORNAGAIN_PYTHON "Build with python support" ON) +option(BORNAGAIN_PYTHON "Build with Python support" ON) option(BA_GUI "Build a graphical user interface" ON) option(BA_TIFF_SUPPORT "Tiff files read/write support" ON) option(BA_TESTS "Build tests" ON) @@ -211,7 +211,7 @@ add_subdirectory(Fit/3rdparty) add_subdirectory(Fit) # other components -set(CoreComponents "Base;Param;Sample;Resample;Device;Sim") +set(CoreComponents "PyCore;Base;Param;Sample;Resample;Device;Sim") set(AllComponents "${CoreComponents};Img3D;GUI") # code analysis -- GitLab From 6e4b6d53e00624e2a25dbd278e2eb2cac1a28867 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 17 Apr 2023 17:41:14 +0200 Subject: [PATCH 19/85] rm old Python-based modules --- Base/Py/PyCore.h | 46 -------- Base/Py/PyObject.h | 28 ----- Base/Py/PyUtil.cpp | 199 --------------------------------- Base/Py/PyUtil.h | 57 ---------- Sample/Multilayer/PyImport.cpp | 124 -------------------- Sample/Multilayer/PyImport.h | 51 --------- 6 files changed, 505 deletions(-) delete mode 100644 Base/Py/PyCore.h delete mode 100644 Base/Py/PyObject.h delete mode 100644 Base/Py/PyUtil.cpp delete mode 100644 Base/Py/PyUtil.h delete mode 100644 Sample/Multilayer/PyImport.cpp delete mode 100644 Sample/Multilayer/PyImport.h diff --git a/Base/Py/PyCore.h b/Base/Py/PyCore.h deleted file mode 100644 index 9aa95b1f6f5..00000000000 --- a/Base/Py/PyCore.h +++ /dev/null @@ -1,46 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file Base/Py/PyCore.h -//! @brief Includes python header and takes care of warnings. -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#ifdef SWIG -#error no need to expose this header to Swig -#endif // SWIG -#ifndef BORNAGAIN_BASE_PY_PYCORE_H -#define BORNAGAIN_BASE_PY_PYCORE_H - -#ifdef BORNAGAIN_PYTHON - -#undef _POSIX_C_SOURCE -#undef _XOPEN_SOURCE - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - -#include <Python.h> -#define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY -#define NO_IMPORT_ARRAY -#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION // to prevent spurious warning -#include <numpy/arrayobject.h> - -#include "auto/Wrap/swig_runtime.h" - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - -#endif // BORNAGAIN_PYTHON - -#endif // BORNAGAIN_BASE_PY_PYCORE_H diff --git a/Base/Py/PyObject.h b/Base/Py/PyObject.h deleted file mode 100644 index ed0aa1a34fb..00000000000 --- a/Base/Py/PyObject.h +++ /dev/null @@ -1,28 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file Base/Py/PyObject.h -//! @brief PyObject forward declaration. -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#ifdef SWIG -#error no need to expose this header to Swig -#endif // SWIG -#ifndef BORNAGAIN_BASE_PY_PYOBJECT_H -#define BORNAGAIN_BASE_PY_PYOBJECT_H - -#ifdef BORNAGAIN_PYTHON -#ifndef PyObject_HEAD -struct _object; -typedef _object PyObject; -#endif -#endif - -#endif // BORNAGAIN_BASE_PY_PYOBJECT_H diff --git a/Base/Py/PyUtil.cpp b/Base/Py/PyUtil.cpp deleted file mode 100644 index 107ee4630cf..00000000000 --- a/Base/Py/PyUtil.cpp +++ /dev/null @@ -1,199 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file Base/Py/PyUtil.cpp -//! @brief IOmplements various functions from PyUtil namespace -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#ifdef BORNAGAIN_PYTHON - -#include "Base/Py/PyUtil.h" -#include "Base/Py/PyCore.h" -#include "Base/Util/Assert.h" -#include "Base/Util/SysUtil.h" -#include <iostream> -#include <sstream> -#include <stdexcept> - -std::string BaseUtil::Python::toString(PyObject* obj) -{ - std::string result; - PyObject* pyStr = PyUnicode_AsEncodedString(obj, "utf-8", "replace"); - if (pyStr != nullptr) { - result = std::string(PyBytes_AsString(pyStr)); - Py_DecRef(pyStr); - } - return result; -} - -std::vector<std::string> BaseUtil::Python::toVectorString(PyObject* obj) -{ - std::vector<std::string> result; - - if (PyTuple_Check(obj)) { - for (Py_ssize_t i = 0; i < PyTuple_Size(obj); i++) { - PyObject* value = PyTuple_GetItem(obj, i); - result.push_back(toString(value)); - } - } else if (PyList_Check(obj)) { - for (Py_ssize_t i = 0; i < PyList_Size(obj); i++) { - PyObject* value = PyList_GetItem(obj, i); - result.push_back(toString(value)); - } - } else - ASSERT(false); // unexpected object - - return result; -} - -std::string BaseUtil::Python::toString(char* c) -{ - if (c) - return c; - return ""; -} - -std::string BaseUtil::Python::toString(wchar_t* c) -{ - if (!c) - return ""; - std::wstring wstr(c); - return std::string(wstr.begin(), wstr.end()); -} - -PyObject* BaseUtil::Python::import_bornagain(const std::vector<std::string>& paths) -{ - if (Py_IsInitialized()) - return nullptr; - - Py_InitializeEx(0); // like Py_Initialize, but skip registration of signal handlers - - if (!paths.empty()) { - PyObject* sysPath = PySys_GetObject((char*)"path"); - for (const std::string& path : paths) - PyList_Append(sysPath, PyString_FromString(path.c_str())); - } - - // Stores signal handler before numpy's mess it up. - // This is to make ctrl-c working from terminal. -#ifndef _WIN32 - PyOS_sighandler_t sighandler; - sighandler = PyOS_getsig(SIGINT); -#endif - - PyObject* pmod = PyImport_ImportModule("bornagain"); - if (!pmod) { - PyErr_Print(); - throw std::runtime_error("Cannot load bornagain"); - } - - // restores single handler to make ctr-c alive. -#ifndef _WIN32 - PyOS_setsig(SIGINT, sighandler); -#endif - - return pmod; -} - -std::string BaseUtil::Python::pythonRuntimeInfo() -{ - Py_InitializeEx(0); - - std::stringstream result; - - // Runtime environment - result << std::string(60, '=') << "\n"; - result << "PATH: " << BaseUtil::System::getenv("PATH") << "\n"; - result << "PYTHONPATH: " << BaseUtil::System::getenv("PYTHONPATH") << "\n"; - result << "PYTHONHOME: " << BaseUtil::System::getenv("PYTHONHOME") << "\n"; - - // Embedded Python details - result << "Py_GetProgramName(): " << BaseUtil::Python::toString(Py_GetProgramName()) << "\n"; - result << "Py_GetProgramFullPath(): " << BaseUtil::Python::toString(Py_GetProgramFullPath()) - << "\n"; - result << "Py_GetPath(): " << BaseUtil::Python::toString(Py_GetPath()) << "\n"; - result << "Py_GetPythonHome(): " << BaseUtil::Python::toString(Py_GetPythonHome()) << "\n"; - - // Runtime Python's sys.path - PyObject* sysPath = PySys_GetObject((char*)"path"); - auto content = BaseUtil::Python::toVectorString(sysPath); - result << "sys.path: "; - for (auto s : content) - result << s << ","; - result << "\n"; - - return result.str(); -} - -// Attempt to retrieve Python stack trace -// https://stackoverflow.com/questions/1796510/accessing-a-python-traceback-from-the-c-api - -std::string BaseUtil::Python::pythonStackTrace() -{ - std::stringstream result; - - if (PyErr_Occurred()) { - PyObject *ptype, *pvalue, *ptraceback, *pystr; - - PyErr_Fetch(&ptype, &pvalue, &ptraceback); - pystr = PyObject_Str(pvalue); - if (char* str = PyString_AsString(pystr)) - result << std::string(str) << "\n"; - - PyObject* module_name = PyString_FromString("traceback"); - PyObject* pyth_module = PyImport_Import(module_name); - Py_DecRef(module_name); - - if (pyth_module) { - result << "\n"; - PyObject* pyth_func = PyObject_GetAttrString(pyth_module, "format_exception"); - if (pyth_func && PyCallable_Check(pyth_func)) { - PyObject* pyth_val = - PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL); - if (pyth_val) { - pystr = PyObject_Str(pyth_val); - if (char* str = PyString_AsString(pystr)) - result << std::string(str); - Py_DecRef(pyth_val); - } - } - result << "\n"; - } - } - - result << "\n"; - result << pythonRuntimeInfo(); - result << "\n"; - - return result.str(); -} - -PyObject* BaseUtil::Python::createNumpyArray(const std::vector<double>& data) -{ - const size_t ndim(1); - npy_int ndim_numpy = ndim; - auto* ndimsizes_numpy = new npy_intp[ndim]; - ndimsizes_numpy[0] = data.size(); - - // creating standalone numpy array - PyObject* pyarray = PyArray_SimpleNew(ndim_numpy, ndimsizes_numpy, NPY_DOUBLE); - delete[] ndimsizes_numpy; - ASSERT(pyarray); - - // getting pointer to data buffer of numpy array - double* array_buffer = (double*)PyArray_DATA((PyArrayObject*)pyarray); - - for (size_t index = 0; index < data.size(); ++index) - *array_buffer++ = data[index]; - - return pyarray; -} - -#endif // BORNAGAIN_PYTHON diff --git a/Base/Py/PyUtil.h b/Base/Py/PyUtil.h deleted file mode 100644 index 54846d5ee2c..00000000000 --- a/Base/Py/PyUtil.h +++ /dev/null @@ -1,57 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file Base/Py/PyUtil.h -//! @brief Defines PyUtil namespace -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#ifdef SWIG -#error no need to expose this header to Swig -#endif // SWIG -#ifndef BORNAGAIN_BASE_PY_PYUTIL_H -#define BORNAGAIN_BASE_PY_PYUTIL_H - -#ifdef BORNAGAIN_PYTHON - -#include "Base/Py/PyObject.h" -#include <memory> -#include <string> -#include <vector> - -class MultiLayer; - -namespace BaseUtil::Python { - -//! Converts PyObject into string, if possible, or throws exception. -std::string toString(PyObject* obj); - -//! Converts PyObject into vector of strings, if possible, or throws exception. -std::vector<std::string> toVectorString(PyObject* obj); - -//! Converts char to string. In the case of nullptr will return an empty string. -std::string toString(char* c); -std::string toString(wchar_t* c); - -//! Imports BornAgain from given location. If path is empty, tries to rely on PYTHONPATH. -PyObject* import_bornagain(const std::vector<std::string>& paths = {}); - -//! Returns multi-line string representing PATH, PYTHONPATH, sys.path and other info. -std::string pythonRuntimeInfo(); - -//! Returns string representing python stack trace. -std::string pythonStackTrace(); - -PyObject* createNumpyArray(const std::vector<double>& data); - -} // namespace BaseUtil::Python - -#endif // BORNAGAIN_PYTHON - -#endif // BORNAGAIN_BASE_PY_PYUTIL_H diff --git a/Sample/Multilayer/PyImport.cpp b/Sample/Multilayer/PyImport.cpp deleted file mode 100644 index 962ea3407ab..00000000000 --- a/Sample/Multilayer/PyImport.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file Sample/Multilayer/PyImport.cpp -//! @brief Implements PyImport namespace -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#ifdef BORNAGAIN_PYTHON - -#include "Sample/Multilayer/PyImport.h" -#include "Base/Py/PyCore.h" -#include "Base/Py/PyUtil.h" -#include "Sample/Multilayer/MultiLayer.h" - -namespace { - -std::string error_description(const std::string& title) -{ - std::stringstream buf; - buf << title << "\n"; - buf << BaseUtil::Python::pythonStackTrace() << "\n"; - return buf.str(); -} - -} // namespace - -std::unique_ptr<MultiLayer> Py::Import::createFromPython(const std::string& script, - const std::string& functionName, - const std::string& path) -{ - BaseUtil::Python::import_bornagain({path}); - - PyObject* pCompiledFn = Py_CompileString(script.c_str(), "", Py_file_input); - if (!pCompiledFn) - throw std::runtime_error(error_description("Cannot compile snippet")); - - // create a module - PyObject* pModule = PyImport_ExecCodeModule((char*)"test", pCompiledFn); - if (!pModule) { - Py_DecRef(pCompiledFn); - throw std::runtime_error(error_description("Cannot exec module")); - } - - // locate the "get_simulation" function (it's an attribute of the module) - PyObject* pAddFn = PyObject_GetAttrString(pModule, functionName.c_str()); - if (!pAddFn) - throw std::runtime_error("Cannot find function " + functionName); - - PyObject* instance = PyObject_CallFunctionObjArgs(pAddFn, NULL); - if (!instance) { - Py_DecRef(pAddFn); - Py_DecRef(pModule); - Py_DecRef(pCompiledFn); - throw std::runtime_error(error_description("Failed running function " + functionName)); - } - - // clean up - Py_DecRef(pAddFn); - Py_DecRef(pModule); - Py_DecRef(pCompiledFn); - - void* argp1 = 0; - swig_type_info* pTypeInfo = SWIG_TypeQuery("MultiLayer *"); - - const int res = SWIG_ConvertPtr(instance, &argp1, pTypeInfo, 0); - if (!SWIG_IsOK(res)) { - Py_DecRef(instance); - throw std::runtime_error("SWIG failed to extract a MultiLayer."); - } - - MultiLayer* sample = reinterpret_cast<MultiLayer*>(argp1); - std::unique_ptr<MultiLayer> result(sample->clone()); - - Py_DecRef(instance); - - return result; -} - -std::vector<std::string> Py::Import::listOfFunctions(const std::string& script, - const std::string& path) -{ - BaseUtil::Python::import_bornagain({path}); - - PyObject* pCompiledFn = Py_CompileString(script.c_str(), "", Py_file_input); - if (!pCompiledFn) - throw std::runtime_error(error_description("Cannot compile snippet")); - - // create a module - PyObject* pModule = PyImport_ExecCodeModule((char*)"test", pCompiledFn); - if (!pModule) { - Py_DecRef(pCompiledFn); - throw std::runtime_error(error_description("Cannot exec module")); - } - - PyObject* dict = PyModule_GetDict(pModule); - if (!dict) - throw std::runtime_error("Cannot get dictionary from module"); - - std::vector<std::string> result; - PyObject *key, *value; - Py_ssize_t pos = 0; - while (PyDict_Next(dict, &pos, &key, &value)) { - if (PyCallable_Check(value)) { - std::string func_name = BaseUtil::Python::toString(key); - if (func_name.find("__") == std::string::npos) - result.push_back(func_name); - } - } - - Py_DecRef(dict); - Py_DecRef(pModule); - Py_DecRef(pCompiledFn); - - return result; -} - -#endif // BORNAGAIN_PYTHON diff --git a/Sample/Multilayer/PyImport.h b/Sample/Multilayer/PyImport.h deleted file mode 100644 index 134e97ed82f..00000000000 --- a/Sample/Multilayer/PyImport.h +++ /dev/null @@ -1,51 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file Sample/Multilayer/PyImport.h -//! @brief Defines PyImport namespace -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#ifdef SWIG -#error no need to expose this header to Swig -#endif // SWIG -#ifndef BORNAGAIN_SAMPLE_MULTILAYER_PYIMPORT_H -#define BORNAGAIN_SAMPLE_MULTILAYER_PYIMPORT_H - -#ifdef BORNAGAIN_PYTHON - -#include <memory> -#include <string> -#include <vector> - -class MultiLayer; - -namespace Py::Import { - -////! Returns directory name -// std::string bornagainPythonDir(); - -//! Creates a multi layer by running python code in embedded interpreter. -//! @param script: Python script -//! @param functionName: A function name in this script which produces a MultiLayer -//! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH -std::unique_ptr<MultiLayer> createFromPython(const std::string& script, - const std::string& functionName, - const std::string& path = ""); - -//! Returns list of functions defined in the script. -//! @param script: Python script -//! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH -std::vector<std::string> listOfFunctions(const std::string& script, const std::string& path = ""); - -} // namespace Py::Import - -#endif // BORNAGAIN_PYTHON - -#endif // BORNAGAIN_SAMPLE_MULTILAYER_PYIMPORT_H -- GitLab From 78612f25cf3c895b158b3d9b0900125ba92f5774 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 17 Apr 2023 17:26:17 +0200 Subject: [PATCH 20/85] fix PyCore/CMakeLists.txt --- PyCore/CMakeLists.txt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/PyCore/CMakeLists.txt b/PyCore/CMakeLists.txt index 18f6637af92..59bb4a00a2c 100644 --- a/PyCore/CMakeLists.txt +++ b/PyCore/CMakeLists.txt @@ -8,17 +8,22 @@ if(NOT BORNAGAIN_PYTHON) endif() set(component PyCore) -set(name PyCore) -set(lib BornAgain${name}) +set(lib BornAgain${component}) # --- source and include files --- # Python-dependent source files -file(GLOB source_files */*.cpp) -file(GLOB include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) +file(GLOB_RECURSE source_files */*.cpp) +file(GLOB_RECURSE include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) + +# --- make the library --- +MakeLib(${component} ${lib} "" ${BORNAGAIN_PYTHON}) target_include_directories(${lib} PRIVATE - ${Python3_INCLUDES} + ${CMAKE_SOURCE_DIR} + ${LibHeinz_INCLUDE_DIR} + ${Python3_INCLUDE_DIRS} + ${Python3_NumPy_INCLUDE_DIRS} ) target_link_libraries(${lib} -- GitLab From 78a170c698a8877353ff06f6481ffcc1a80c87f7 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 24 Apr 2023 13:04:41 +0200 Subject: [PATCH 21/85] PyCore/Embed/PythonInterpreter.cpp: fix the return type of `_init_numpy` function --- PyCore/Embed/PythonInterpreter.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index 50df04d84fc..94fe051e2db 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -34,7 +34,7 @@ namespace { //-- Auxiliary functions // thin wrapper to initialize Numpy -int _init_numpy() +std::nullptr_t _init_numpy(int& status) { if(!PyArray_API) { /* To use Numpy Array C-API from an extension module, @@ -44,12 +44,16 @@ int _init_numpy() This function imports the module where the function-pointer table is stored and points the correct variable to it. */ - // NOTE: import_array returns `NULL` on failure. + // NOTE: Numpy's `import_array` returns `NULL` on failure; + // hence return type must be `std::nullptr_t`. + status = 0; import_array(); - return 1; // Numpy Array API loaded successfully + status = 1; // Numpy Array API loaded successfully + return NULL; } - return 2; // Numpy Array API is already loaded + status = 2; // Numpy Array API is already loaded + return NULL; } @@ -519,7 +523,8 @@ Status PythonInterpreter::Numpy::initialize() { if(py_status.isError()) return py_status; - const int res {_init_numpy()}; + int res; + _init_numpy(res); switch(res) { -- GitLab From b32426b9b81163256feedb4c863da113b08437d1 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 17 Apr 2023 18:02:33 +0200 Subject: [PATCH 22/85] Adapt MakeLib.cmake --- cmake/BornAgain/MakeLib.cmake | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/cmake/BornAgain/MakeLib.cmake b/cmake/BornAgain/MakeLib.cmake index db4348fc3e7..322fdfc01c4 100644 --- a/cmake/BornAgain/MakeLib.cmake +++ b/cmake/BornAgain/MakeLib.cmake @@ -1,8 +1,13 @@ # Configure one component library. -function(MakeLib name lib tmpdir withPython) +function(MakeLib name lib swigtmpdir withPython) + + # NOTE: `source_files` and `include_files` are defined in the parent scope + + string(STRIP "${swigtmpdir}" swigtmpdir) + + if(withPython AND swigtmpdir) - if(withPython) if((CLANG) OR (GCC)) # suppress warnings from auto-generated code (last updated for Swig 4.0.1) set_source_files_properties(${AUTO_DIR}/lib${lib}_wrap.cpp @@ -52,14 +57,22 @@ function(MakeLib name lib tmpdir withPython) set_target_properties(${lib} PROPERTIES LINK_FLAGS ${link_flags}) endif() - if(withPython) - SwigLib(${name} ${lib} ${tmpdir}) - if(BA_PY_PACKAGE) - # add the BornAgain library to the Python wheel - add_library_to_wheel(${lib}) - endif() + if(BORNAGAIN_PYTHON) + target_compile_definitions(${lib} PRIVATE -DBORNAGAIN_PYTHON) + endif() + + # SWIG-produced interface + if(swigtmpdir) + SwigLib(${name} ${lib} ${swigtmpdir}) + endif() + + # Python package + if(BA_PY_PACKAGE) + # add the BornAgain library to the Python wheel + add_library_to_wheel(${lib}) endif() + # installation install(TARGETS ${lib} LIBRARY DESTINATION ${destination_lib} COMPONENT Libraries RUNTIME DESTINATION ${destination_lib} COMPONENT Libraries) -- GitLab From 140937ee04ae932f096e7811087d5b0b71ed931c Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 18 Apr 2023 16:48:47 +0200 Subject: [PATCH 23/85] Adapt MakePythonWheel --- cmake/multipython/MakePythonWheel.cmake | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cmake/multipython/MakePythonWheel.cmake b/cmake/multipython/MakePythonWheel.cmake index 89d94cf0198..8cd35a71e6c 100644 --- a/cmake/multipython/MakePythonWheel.cmake +++ b/cmake/multipython/MakePythonWheel.cmake @@ -141,11 +141,21 @@ function(add_library_to_wheel lib) # copy the shared library and its Python interface to the Python wheel folder set(_dst ${BA_PY_LIBRARY_OUTPUT_DIR}/) + add_custom_command(TARGET ${lib} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${lib}>" ${_dst}/${libfilename} - COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_PROPERTY:${lib},_LIBRARY_PY>" ${_dst} COMMENT "Add library ${lib} to the Python wheel..." + ) + + get_target_property(lib_LIBRARY_PY ${lib} _LIBRARY_PY) + string(STRIP ${lib_LIBRARY_PY} lib_LIBRARY_PY) + if(lib_LIBRARY_PY) + add_custom_command(TARGET ${lib} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_PROPERTY:${lib},_LIBRARY_PY>" ${_dst} + COMMENT "Add Python wrapper-script of library ${lib} to the Python wheel..." ) + endif() endfunction() -- GitLab From 589ca747df1d23bbe9a393271db29b2dc02e93cf Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 18 Apr 2023 17:10:59 +0200 Subject: [PATCH 24/85] Adapt Device/CMakeLists.txt --- Device/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Device/CMakeLists.txt b/Device/CMakeLists.txt index 07952cf0864..929a4a9bd76 100644 --- a/Device/CMakeLists.txt +++ b/Device/CMakeLists.txt @@ -21,6 +21,7 @@ set(${lib}_LIBRARY ${lib} PARENT_SCOPE) target_link_libraries(${lib} PUBLIC BornAgainBase + BornAgainPyCore BornAgainFit BornAgainParam BornAgainSample -- GitLab From cecd57eb19c9034e0e207372a91b01bbe27b1d1c Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 17 Apr 2023 18:02:25 +0200 Subject: [PATCH 25/85] Adapt Device/Data/Datafield module --- Device/Data/Datafield.cpp | 24 +++++++++--------------- Device/Data/Datafield.h | 2 ++ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Device/Data/Datafield.cpp b/Device/Data/Datafield.cpp index 4309b20f374..336b82c9d57 100644 --- a/Device/Data/Datafield.cpp +++ b/Device/Data/Datafield.cpp @@ -15,7 +15,6 @@ #include "Device/Data/Datafield.h" #include "Base/Axis/Frame.h" #include "Base/Axis/Scale.h" -#include "Base/Py/PyCore.h" #include "Base/Util/Assert.h" #include <algorithm> @@ -206,11 +205,9 @@ Datafield* Datafield::crop(double xmin, double xmax) const #ifdef BORNAGAIN_PYTHON -#include "PyCore/Embed/PyObjectDecl.h" #include "PyCore/Embed/PythonInterpreter.h" // Numpy::arrayND, Numpy::getDataPtr -template <> -PyObject* OutputData<double>::getArray() const +PyObject* Datafield::npArray() const { // TODO: Thoroughly check this function regarding index manipulations @@ -237,21 +234,18 @@ PyObject* OutputData<double>::getArray() const return nullptr; double* array_buffer = data.val; - const std::size_t idxMax = getAllocatedSize(); + // filling numpy array with output_data if (rank() == 2) { - for (size_t idx = 0; idx < idxMax; ++idx) { - const std::vector<int> axes_indices = getAxesBinIndices(idx); - const std::size_t ax0_size = m_axes[0]->size(), ax1_size = m_axes[1]->size(), - offset = - axes_indices[0] + ax0_size * (ax1_size - 1 - axes_indices[1]); - array_buffer[offset] = (*this)[idx]; + for (size_t i = 0; i < size(); ++i) { + std::vector<int> axes_indices = frame().allIndices(i); + size_t offset = + axes_indices[0] + axis(0).size() * (axis(1).size() - 1 - axes_indices[1]); + array_buffer[offset] = (*this)[i]; } } else if (rank() == 1) { - for (size_t idx = 0; idx < idxMax; ++idx) { - *array_buffer = (*this)[idx]; - ++array_buffer; - } + for (size_t i = 0; i < size(); ++i) + *array_buffer++ = (*this)[i]; } else { return nullptr; } diff --git a/Device/Data/Datafield.h b/Device/Data/Datafield.h index 877fa8df262..9acb6ba3f72 100644 --- a/Device/Data/Datafield.h +++ b/Device/Data/Datafield.h @@ -18,6 +18,8 @@ #include <memory> #include <vector> +#include "PyCore/Embed/PyObjectDecl.h" + using std::size_t; class Scale; -- GitLab From 5a76608764a1e961124062a51f34ef88143b2496 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 21 Apr 2023 16:51:08 +0200 Subject: [PATCH 26/85] MakeLib: simplify interface and avoid global variables --- Base/CMakeLists.txt | 14 ++++++++++---- Device/CMakeLists.txt | 14 ++++++++++---- Fit/CMakeLists.txt | 14 ++++++++++---- Param/CMakeLists.txt | 14 ++++++++++---- PyCore/CMakeLists.txt | 11 ++++++++++- Resample/CMakeLists.txt | 14 ++++++++++---- Sample/CMakeLists.txt | 14 ++++++++++---- Sim/CMakeLists.txt | 14 ++++++++++---- cmake/BornAgain/MakeLib.cmake | 24 +++++++++++++++++++++--- 9 files changed, 101 insertions(+), 32 deletions(-) diff --git a/Base/CMakeLists.txt b/Base/CMakeLists.txt index 9c145e15c68..172f74d8d90 100644 --- a/Base/CMakeLists.txt +++ b/Base/CMakeLists.txt @@ -10,11 +10,17 @@ set(lib BornAgain${component}) file(GLOB source_files */*.cpp) file(GLOB include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) -# --- make the library --- - -MakeLib(${component} ${lib} ${CMAKE_CURRENT_BINARY_DIR}/Wrap ${BORNAGAIN_PYTHON}) +#--- define target properties --- +add_library(${lib} SHARED) +set_target_properties(${lib} PROPERTIES + COMPONENT_NAME ${component} + SWIG_TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/Wrap" + SOURCE_FILES "${source_files}" + INCLUDE_FILES "${include_files}" + EMBED_PYTHON ${BORNAGAIN_PYTHON}) -set(${lib}_LIBRARY ${lib} PARENT_SCOPE) +# --- make the library --- +MakeLib(${lib}) # --- external dependences --- diff --git a/Device/CMakeLists.txt b/Device/CMakeLists.txt index 929a4a9bd76..d0159f8470e 100644 --- a/Device/CMakeLists.txt +++ b/Device/CMakeLists.txt @@ -10,11 +10,17 @@ set(lib BornAgain${component}) file(GLOB source_files */*.cpp) file(GLOB include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) -# --- make the library --- - -MakeLib(${component} ${lib} ${CMAKE_CURRENT_BINARY_DIR}/Wrap ${BORNAGAIN_PYTHON}) +# --- define target properties --- +add_library(${lib} SHARED) +set_target_properties(${lib} PROPERTIES + COMPONENT_NAME ${component} + SWIG_TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/Wrap" + SOURCE_FILES "${source_files}" + INCLUDE_FILES "${include_files}" + EMBED_PYTHON ${BORNAGAIN_PYTHON}) -set(${lib}_LIBRARY ${lib} PARENT_SCOPE) +# --- make the library --- +MakeLib(${lib}) # --- external dependencies --- diff --git a/Fit/CMakeLists.txt b/Fit/CMakeLists.txt index 6cbc90450d0..276a7b279bd 100644 --- a/Fit/CMakeLists.txt +++ b/Fit/CMakeLists.txt @@ -10,11 +10,17 @@ set(lib BornAgain${component}) file(GLOB source_files */*.cpp) file(GLOB include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) -# --- make the library --- - -MakeLib(${component} ${lib} ${CMAKE_CURRENT_BINARY_DIR}/Wrap ${BORNAGAIN_PYTHON}) +# --- define target properties --- +add_library(${lib} SHARED) +set_target_properties(${lib} PROPERTIES + COMPONENT_NAME ${component} + SWIG_TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/Wrap" + SOURCE_FILES "${source_files}" + INCLUDE_FILES "${include_files}" + EMBED_PYTHON ${BORNAGAIN_PYTHON}) -set(${lib}_LIBRARY ${lib} PARENT_SCOPE) +# --- make the library --- +MakeLib(${lib}) # --- external dependences --- diff --git a/Param/CMakeLists.txt b/Param/CMakeLists.txt index 5faa98a59cd..be8c78debc6 100644 --- a/Param/CMakeLists.txt +++ b/Param/CMakeLists.txt @@ -10,11 +10,17 @@ set(lib BornAgain${component}) file(GLOB source_files */*.cpp) file(GLOB include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) -# --- make the library --- - -MakeLib(${component} ${lib} ${CMAKE_CURRENT_BINARY_DIR}/Wrap ${BORNAGAIN_PYTHON}) +# --- define target properties --- +add_library(${lib} SHARED) +set_target_properties(${lib} PROPERTIES + COMPONENT_NAME ${component} + SWIG_TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/Wrap" + SOURCE_FILES "${source_files}" + INCLUDE_FILES "${include_files}" + EMBED_PYTHON ${BORNAGAIN_PYTHON}) -set(${lib}_LIBRARY ${lib} PARENT_SCOPE) +# --- make the library --- +MakeLib(${lib}) # --- external dependences --- diff --git a/PyCore/CMakeLists.txt b/PyCore/CMakeLists.txt index 59bb4a00a2c..2ad73e555ce 100644 --- a/PyCore/CMakeLists.txt +++ b/PyCore/CMakeLists.txt @@ -15,8 +15,17 @@ set(lib BornAgain${component}) file(GLOB_RECURSE source_files */*.cpp) file(GLOB_RECURSE include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) +# --- define target properties --- +add_library(${lib} SHARED) +set_target_properties(${lib} PROPERTIES + COMPONENT_NAME ${component} + SWIG_TMP_DIR "" + SOURCE_FILES "${source_files}" + INCLUDE_FILES "${include_files}" + EMBED_PYTHON ${BORNAGAIN_PYTHON}) + # --- make the library --- -MakeLib(${component} ${lib} "" ${BORNAGAIN_PYTHON}) +MakeLib(${lib}) target_include_directories(${lib} PRIVATE diff --git a/Resample/CMakeLists.txt b/Resample/CMakeLists.txt index b93d3c75fdf..a94ffb57b5b 100644 --- a/Resample/CMakeLists.txt +++ b/Resample/CMakeLists.txt @@ -10,11 +10,17 @@ set(lib BornAgain${component}) file(GLOB source_files */*.cpp) file(GLOB include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) -# --- make the library --- - -MakeLib(${component} ${lib} ${CMAKE_CURRENT_BINARY_DIR}/Wrap ${BORNAGAIN_PYTHON}) +# --- define target properties --- +add_library(${lib} SHARED) +set_target_properties(${lib} PROPERTIES + COMPONENT_NAME ${component} + SWIG_TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/Wrap" + SOURCE_FILES "${source_files}" + INCLUDE_FILES "${include_files}" + EMBED_PYTHON ${BORNAGAIN_PYTHON}) -set(${lib}_LIBRARY ${lib} PARENT_SCOPE) +# --- make the library --- +MakeLib(${lib}) # --- external dependences --- diff --git a/Sample/CMakeLists.txt b/Sample/CMakeLists.txt index a9c71c09920..b21d7ebaac4 100644 --- a/Sample/CMakeLists.txt +++ b/Sample/CMakeLists.txt @@ -10,11 +10,17 @@ set(lib BornAgain${component}) file(GLOB source_files */*.cpp) file(GLOB include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) -# --- make the library --- - -MakeLib(${component} ${lib} ${CMAKE_CURRENT_BINARY_DIR}/Wrap ${BORNAGAIN_PYTHON}) +# --- define target properties --- +add_library(${lib} SHARED) +set_target_properties(${lib} PROPERTIES + COMPONENT_NAME ${component} + SWIG_TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/Wrap" + SOURCE_FILES "${source_files}" + INCLUDE_FILES "${include_files}" + EMBED_PYTHON ${BORNAGAIN_PYTHON}) -set(${lib}_LIBRARY ${lib} PARENT_SCOPE) +# --- make the library --- +MakeLib(${lib}) # --- external dependences --- diff --git a/Sim/CMakeLists.txt b/Sim/CMakeLists.txt index 4812c7c587a..27d5790dde6 100644 --- a/Sim/CMakeLists.txt +++ b/Sim/CMakeLists.txt @@ -10,11 +10,17 @@ set(lib BornAgain${component}) file(GLOB source_files */*.cpp) file(GLOB include_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */*.h) -# --- make the library --- - -MakeLib(${component} ${lib} ${CMAKE_CURRENT_BINARY_DIR}/Wrap ${BORNAGAIN_PYTHON}) +# --- define target properties --- +add_library(${lib} SHARED) +set_target_properties(${lib} PROPERTIES + COMPONENT_NAME ${component} + SWIG_TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/Wrap" + SOURCE_FILES "${source_files}" + INCLUDE_FILES "${include_files}" + EMBED_PYTHON ${BORNAGAIN_PYTHON}) -set(${lib}_LIBRARY ${lib} PARENT_SCOPE) +# --- make the library --- +MakeLib(${lib}) # --- external dependences --- diff --git a/cmake/BornAgain/MakeLib.cmake b/cmake/BornAgain/MakeLib.cmake index 322fdfc01c4..ecf4520b05d 100644 --- a/cmake/BornAgain/MakeLib.cmake +++ b/cmake/BornAgain/MakeLib.cmake @@ -1,8 +1,13 @@ # Configure one component library. -function(MakeLib name lib swigtmpdir withPython) +function(MakeLib lib) - # NOTE: `source_files` and `include_files` are defined in the parent scope + # get target properties + get_target_property(name ${lib} COMPONENT_NAME) + get_target_property(swigtmpdir ${lib} SWIG_TMP_DIR) + get_target_property(withPython ${lib} EMBED_PYTHON) + get_target_property(source_files ${lib} SOURCE_FILES) + get_target_property(include_files ${lib} INCLUDE_FILES) string(STRIP "${swigtmpdir}" swigtmpdir) @@ -22,8 +27,8 @@ function(MakeLib name lib swigtmpdir withPython) list(APPEND source_files ${AUTO_DIR}/lib${lib}_wrap.cpp) endif() - add_library(${lib} SHARED ${source_files}) set_target_properties(${lib} PROPERTIES + SOURCES "${source_files}" PREFIX ${libprefix} SUFFIX ${libsuffix} OUTPUT_NAME ${lib} @@ -72,6 +77,19 @@ function(MakeLib name lib swigtmpdir withPython) add_library_to_wheel(${lib}) endif() + if(WIN32 AND BORNAGAIN_PYTHON) + # python in windows required .pyd extension for the library name + install(FILES ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${libprefix}${lib}.pyd + DESTINATION ${destination_python} COMPONENT Libraries) + add_custom_command( + TARGET ${lib} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/bin/${libprefix}${lib}${libsuffix} + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${libprefix}${lib}${libsuffix} + ) + endif() + # installation install(TARGETS ${lib} LIBRARY DESTINATION ${destination_lib} COMPONENT Libraries -- GitLab From 67369d953b20ba8e9ea03e0134ac482a1ea4440f Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 21 Apr 2023 14:31:07 +0200 Subject: [PATCH 27/85] SwigLib: move compile-flag setting and Windows post-build and installation commands to MakeLib --- cmake/BornAgain/SwigLib.cmake | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/cmake/BornAgain/SwigLib.cmake b/cmake/BornAgain/SwigLib.cmake index c9491a3a894..e53614d9a02 100644 --- a/cmake/BornAgain/SwigLib.cmake +++ b/cmake/BornAgain/SwigLib.cmake @@ -1,17 +1,17 @@ # Configure Python bindings for one component library. # Called from function MakeLib. -function(SwigLib name lib tmpdir) +function(SwigLib name lib swigtmpdir) if(NOT BORNAGAIN_PYTHON) message(FATAL_ERROR "Function SwigLib called though BORNAGAIN_PYTHON=false") endif() - message(STATUS "SwigLib ${name}: ${lib} ${tmpdir}") + message(STATUS "SwigLib ${name}: ${lib} ${swigtmpdir}") if(CONFIGURE_BINDINGS) - file(MAKE_DIRECTORY ${tmpdir}) + file(MAKE_DIRECTORY ${swigtmpdir}) # static (manually written) input files set(swig_dependencies @@ -30,19 +30,19 @@ function(SwigLib name lib tmpdir) # Please keep -Werror, in order not to overlook critical warnings. # Dispensable warnings are disabled in Wrap/Swig/warnings.i. # Joachim, oct20. - set(SWIG_FLAGS "-c++;-python;-Werror;-o;${AUTO_DIR}/lib${lib}_wrap.cpp;-outdir;${tmpdir}" + set(SWIG_FLAGS "-c++;-python;-Werror;-o;${AUTO_DIR}/lib${lib}_wrap.cpp;-outdir;${swigtmpdir}" ";-I${LibHeinz_INCLUDE_DIR}" ";-I${CMAKE_SOURCE_DIR};-I${CMAKE_BINARY_DIR}/inc") add_custom_command( OUTPUT ${AUTO_DIR}/lib${lib}.py COMMAND ${Python3_EXECUTABLE} ${SWIG_DIR}/tweaks.py - ${tmpdir}/lib${lib}.py + ${swigtmpdir}/lib${lib}.py ${AUTO_DIR}/lib${lib}.py - DEPENDS ${tmpdir}/lib${lib}.py + DEPENDS ${swigtmpdir}/lib${lib}.py ) add_custom_command( - OUTPUT ${tmpdir}/lib${lib}.py + OUTPUT ${swigtmpdir}/lib${lib}.py ${AUTO_DIR}/lib${lib}_wrap.h ${AUTO_DIR}/lib${lib}_wrap.cpp COMMAND ${SWIG_EXECUTABLE} ${SWIG_FLAGS} ${SWIG_DIR}/lib${lib}.i @@ -68,26 +68,10 @@ function(SwigLib name lib tmpdir) add_dependencies(${lib} ${lib}_python) - target_compile_definitions(${lib} PUBLIC -DBORNAGAIN_PYTHON) target_include_directories(${lib} PRIVATE ${Python3_INCLUDE_DIRS} ${Python3_NumPy_INCLUDE_DIRS}) target_link_libraries(${lib} PRIVATE ${Python3_LIBRARIES}) install(FILES ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${lib}.py DESTINATION ${destination_python} COMPONENT Libraries) # required by swig - if(WIN32) - # python in windows required .pyd extension for the library name - if(BORNAGAIN_PYTHON) - install(FILES ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${libprefix}${lib}.pyd - DESTINATION ${destination_python} COMPONENT Libraries) - add_custom_command( - TARGET ${lib} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_BINARY_DIR}/bin/${libprefix}${lib}${libsuffix} - ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${libprefix}${lib}${libsuffix} - ) - endif() - endif() - endfunction() -- GitLab From c1f27b82d6afd7d30c624da4b695d9c015c31b53 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 21 Apr 2023 17:08:33 +0200 Subject: [PATCH 28/85] SwigLib: minor improvements, add comments and TODO --- cmake/BornAgain/SwigLib.cmake | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cmake/BornAgain/SwigLib.cmake b/cmake/BornAgain/SwigLib.cmake index e53614d9a02..2c76ed79543 100644 --- a/cmake/BornAgain/SwigLib.cmake +++ b/cmake/BornAgain/SwigLib.cmake @@ -3,6 +3,7 @@ function(SwigLib name lib swigtmpdir) + # TODO: Why BORNAGAIN_PYTHON is needed for Swig API? if(NOT BORNAGAIN_PYTHON) message(FATAL_ERROR "Function SwigLib called though BORNAGAIN_PYTHON=false") endif() @@ -55,6 +56,11 @@ function(SwigLib name lib swigtmpdir) endif() + # include Python and Numpy headers and link to Python shared library + target_include_directories(${lib} PRIVATE ${Python3_INCLUDE_DIRS} ${Python3_NumPy_INCLUDE_DIRS}) + target_link_libraries(${lib} PRIVATE ${Python3_LIBRARIES}) + + # copy SWIG-produced Python wrapper script to the library output directory add_custom_target( ${lib}_python COMMAND ${CMAKE_COMMAND} @@ -68,10 +74,7 @@ function(SwigLib name lib swigtmpdir) add_dependencies(${lib} ${lib}_python) - target_include_directories(${lib} PRIVATE ${Python3_INCLUDE_DIRS} ${Python3_NumPy_INCLUDE_DIRS}) - target_link_libraries(${lib} PRIVATE ${Python3_LIBRARIES}) - install(FILES ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${lib}.py - DESTINATION ${destination_python} COMPONENT Libraries) # required by swig + DESTINATION ${destination_python} COMPONENT Libraries) endfunction() -- GitLab From b40cf63dc92830ad921b5ee70349d48c8d80db5d Mon Sep 17 00:00:00 2001 From: "a.nejati.debug@debian-ci" <a.nejati.debug@debian-ci> Date: Mon, 24 Apr 2023 15:24:58 +0200 Subject: [PATCH 29/85] Apply clang-format --- GUI/View/Project/PyImportAssistant.cpp | 22 +- PyCore/Embed/PyCore.h | 6 +- PyCore/Embed/PyObjectDecl.h | 2 +- PyCore/Embed/PyObjectPtr.cpp | 40 +-- PyCore/Embed/PyObjectPtr.h | 19 +- PyCore/Embed/PythonInterpreter.cpp | 275 ++++++++---------- PyCore/Embed/PythonInterpreter.h | 25 +- PyCore/Embed/Result.cpp | 8 +- PyCore/Embed/Result.h | 45 +-- PyCore/Sample/Multilayer/ImportMultiLayer.cpp | 23 +- PyCore/Sample/Multilayer/ImportMultiLayer.h | 15 +- 11 files changed, 225 insertions(+), 255 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index d7bf56269ce..a6a0028e8de 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -29,9 +29,9 @@ #include "GUI/View/Info/MessageBox.h" #include "GUI/View/Project/ProjectManager.h" #include "GUI/View/Tool/Globals.h" +#include "PyCore/Embed/PythonInterpreter.h" // listOfFunctions +#include "PyCore/Sample/Multilayer/ImportMultiLayer.h" // createMultiLayerFromPython #include "Sample/Multilayer/MultiLayer.h" -#include "PyCore/Embed/PythonInterpreter.h" // listOfFunctions -#include "PyCore/Sample/Multilayer/ImportMultiLayer.h" // createMultiLayerFromPython #include <QApplication> #include <QFileDialog> #include <QTextStream> @@ -104,16 +104,14 @@ std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QStri { QApplication::setOverrideCursor(Qt::WaitCursor); - Result<std::unique_ptr<MultiLayer>> result{ - PythonInterpreter::createMultiLayerFromPython( - snippet.toStdString(), funcName.toStdString(), - bornagainDir())}; + Result<std::unique_ptr<MultiLayer>> result{PythonInterpreter::createMultiLayerFromPython( + snippet.toStdString(), funcName.toStdString(), bornagainDir())}; if (!result.valid()) { QApplication::restoreOverrideCursor(); QString message("Exception thrown while executing Python code to create sample.\n\n"); QString details = QString::fromStdString(result.status.message); - DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, details).exec(); + DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, details).exec(); } QApplication::restoreOverrideCursor(); @@ -153,12 +151,10 @@ QString getPySampleFunctionName(const QString& snippet) { QApplication::setOverrideCursor(Qt::WaitCursor); - Result<std::vector<std::string>> funcs_res { - PythonInterpreter::BornAgain::listOfFunctions( - snippet.toStdString(), bornagainDir())}; + Result<std::vector<std::string>> funcs_res{ + PythonInterpreter::BornAgain::listOfFunctions(snippet.toStdString(), bornagainDir())}; - if(!funcs_res.valid()) - { + if (!funcs_res.valid()) { QApplication::restoreOverrideCursor(); QString message("Exception thrown while acquiring functions from Python code.\n\n"); QString details = QString::fromStdString(funcs_res.status.message); @@ -167,7 +163,7 @@ QString getPySampleFunctionName(const QString& snippet) } QApplication::restoreOverrideCursor(); - QStringList funcList {GUI::Util::String::fromStdStrings(funcs_res.val)}; + QStringList funcList{GUI::Util::String::fromStdStrings(funcs_res.val)}; return selectPySampleFunction(funcList); } diff --git a/PyCore/Embed/PyCore.h b/PyCore/Embed/PyCore.h index 10533b160a0..42027dff657 100644 --- a/PyCore/Embed/PyCore.h +++ b/PyCore/Embed/PyCore.h @@ -52,6 +52,6 @@ #pragma GCC diagnostic pop #endif -#endif // BORNAGAIN_PYTHON -#endif // BORNAGAIN_PYTOOLS_PYCORE_H -#endif // USER_API +#endif // BORNAGAIN_PYTHON +#endif // BORNAGAIN_PYTOOLS_PYCORE_H +#endif // USER_API diff --git a/PyCore/Embed/PyObjectDecl.h b/PyCore/Embed/PyObjectDecl.h index 8fdd48f8d38..dfdd0e71c31 100644 --- a/PyCore/Embed/PyObjectDecl.h +++ b/PyCore/Embed/PyObjectDecl.h @@ -10,7 +10,7 @@ struct _object; typedef _object PyObject; #endif -typedef long int np_size_t; // size type used in Numpy +typedef long int np_size_t; // size type used in Numpy #endif // PYOBJECT_H #endif // USER_API diff --git a/PyCore/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp index 55390c99ab5..e7e908a1cc3 100644 --- a/PyCore/Embed/PyObjectPtr.cpp +++ b/PyCore/Embed/PyObjectPtr.cpp @@ -1,31 +1,32 @@ #include "PyObjectPtr.h" #include "PythonInterpreter.h" -#include <string> -#include <sstream> // stringstream #include <exception> +#include <sstream> // stringstream +#include <string> -PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr, - const Status& status): - ptr{pyobject_ptr}, - status{status} +PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr, const Status& status) + : ptr{pyobject_ptr} + , status{status} { // if PyObject pointer is null but result type is not `None` or `Error`, // then that will be an `Error` case. - if(!ptr && status.type != Status::Type::None - && status.type != Status::Type::Error) - { + if (!ptr && status.type != Status::Type::None && status.type != Status::Type::Error) { this->status.type = Status::Type::Error; } } -PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr): - PyObjectPtr(pyobject_ptr, Status(Status::Type::Info)) {} +PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr) + : PyObjectPtr(pyobject_ptr, Status(Status::Type::Info)) +{ +} -PyObjectPtr::PyObjectPtr(const Status& status): - PyObjectPtr(nullptr, status) {} +PyObjectPtr::PyObjectPtr(const Status& status) + : PyObjectPtr(nullptr, status) +{ +} PyObjectPtr::~PyObjectPtr() @@ -34,8 +35,8 @@ PyObjectPtr::~PyObjectPtr() } -PyObjectPtr::PyObjectPtr(PyObjectPtr&& other): - PyObjectPtr(other.ptr, other.status) +PyObjectPtr::PyObjectPtr(PyObjectPtr&& other) + : PyObjectPtr(other.ptr, other.status) { // reset the moved object other.reset(); @@ -44,7 +45,7 @@ PyObjectPtr::PyObjectPtr(PyObjectPtr&& other): PyObject* PyObjectPtr::release() { - PyObject* pyobject_ptr {ptr}; + PyObject* pyobject_ptr{ptr}; reset(); return pyobject_ptr; } @@ -60,10 +61,9 @@ void PyObjectPtr::reset() void PyObjectPtr::discard() { if (!PythonInterpreter::isInitialized()) { - throw(std::runtime_error( - "Decrementing Python reference-count without " - "Python initialized leads to memory access violation " - "(segmentation fault)")); + throw(std::runtime_error("Decrementing Python reference-count without " + "Python initialized leads to memory access violation " + "(segmentation fault)")); } PythonInterpreter::DecRef(ptr); diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 2165b64770a..41ffdb04eb3 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -2,20 +2,19 @@ #define PYOBJECTPTR_H #include "PyObjectDecl.h" -#include "Result.h" // Status +#include "Result.h" // Status #include <vector> //======================================== // safe container for PyObjects /* -The class `PyObjectPtr` contains a `PyObject*` (or `NULL`) and a `Status` which denotes the status of the value (OK/Warning/Error). -Decrementing Python reference-count is performed automatically when a -`PyObjectPtr` expires. +The class `PyObjectPtr` contains a `PyObject*` (or `NULL`) and a `Status` which denotes the status +of the value (OK/Warning/Error). Decrementing Python reference-count is performed automatically when +a `PyObjectPtr` expires. */ -class PyObjectPtr -{ +class PyObjectPtr { public: // raw pointer to the PyObject @@ -40,7 +39,8 @@ public: PyObjectPtr(PyObjectPtr&& other); // reset the container to the initial status (does _not_ release the Python resource) void reset(); - // reset the container to the initial status and return the PyObject pointer (does _not_ release the Python resource) + // reset the container to the initial status and return the PyObject pointer (does _not_ release + // the Python resource) PyObject* release(); // discards the Python resource (decrements the Python reference) void discard(); @@ -52,8 +52,7 @@ public: // Numpy array descriptor -struct NpArrayDescr -{ +struct NpArrayDescr { // Numpy array descriptors bool C_contiguous = false; bool F_contiguous = false; @@ -69,4 +68,4 @@ struct NpArrayDescr bool owns_data = false; }; -#endif // PYOBJECTPTR_H +#endif // PYOBJECTPTR_H diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index 94fe051e2db..74d6558405a 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -13,20 +13,20 @@ References: - Python's garbage collector <https://rushter.com/blog/python-garbage-collector> */ -#define INCLUDE_NUMPY // also include Numpy via PyCore +#define INCLUDE_NUMPY // also include Numpy via PyCore #include "PyCore.h" #undef INCLUDE_NUMPY #include "PythonInterpreter.h" -#include <cstddef> // NULL -#include <cstring> // memcpy -#include <string> +#include <cstddef> // NULL +#include <cstring> // memcpy +#include <memory> // unique_ptr #include <sstream> +#include <string> #include <vector> -#include <memory> // unique_ptr -#include <iostream> // DEBUG +#include <iostream> // DEBUG //======================================== @@ -36,7 +36,7 @@ namespace { // thin wrapper to initialize Numpy std::nullptr_t _init_numpy(int& status) { - if(!PyArray_API) { + if (!PyArray_API) { /* To use Numpy Array C-API from an extension module, `import_array` function must be called. If the extension module is self-contained in a single .c file, @@ -90,23 +90,21 @@ Result<std::vector<std::string>> toVectorString(PyObject* py_object) result.push_back(PythonInterpreter::pyStrtoString(value)); } } else - return {Status(Status::Type::Error, - "Cannnot convert the given Python object " - "to vector<string>.")}; + return {Status(Status::Type::Error, "Cannnot convert the given Python object " + "to vector<string>.")}; return {std::move(result)}; } // auxiliary function to convert a Numpy array data of real type `real_t` // to vector<double>. -template<typename real_t> -inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, - npy_intp rowIdxMax, npy_intp colIdxMax, - std::vector<double>& vector_out) +template <typename real_t> +inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, npy_intp rowIdxMax, + npy_intp colIdxMax, std::vector<double>& vector_out) { std::size_t idx = 0; - for(npy_intp i_row = 0; i_row < rowIdxMax; ++i_row) { - for(npy_intp j_col = 0; j_col < colIdxMax; ++j_col) { + for (npy_intp i_row = 0; i_row < rowIdxMax; ++i_row) { + for (npy_intp j_col = 0; j_col < colIdxMax; ++j_col) { const double val = static_cast<double>( *reinterpret_cast<real_t*>(PyArray_GETPTR2(npArray_ptr, i_row, j_col))); vector_out[idx] = val; @@ -118,12 +116,12 @@ inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, double* npyArray_to_CArray_WIP(PyObjectPtr& npy_array) { - if(!npy_array.valid() || !PyArray_Check(npy_array.ptr)) { + if (!npy_array.valid() || !PyArray_Check(npy_array.ptr)) { throw std::runtime_error( "PythonInterpreter: Cannot convert an invalid Numpy array to a C-Array"); } - PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(npy_array.ptr)}; + PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(npy_array.ptr)}; PyArray_Descr* npArray_descr = PyArray_DESCR(npArray_ptr); const int npArray_type = PyArray_TYPE(npArray_ptr); const int npArray_ndim = PyArray_NDIM(npArray_ptr); @@ -132,33 +130,33 @@ double* npyArray_to_CArray_WIP(PyObjectPtr& npy_array) // DEBUG // type checking - if(PyDataType_ISUNSIGNED(npArray_descr)) + if (PyDataType_ISUNSIGNED(npArray_descr)) printf("AN>> Array datatype = uint\n"); - else if(PyDataType_ISSIGNED(npArray_descr)) + else if (PyDataType_ISSIGNED(npArray_descr)) printf("AN>> Array datatype = int\n"); - else if(PyDataType_ISFLOAT(npArray_descr)) + else if (PyDataType_ISFLOAT(npArray_descr)) printf("AN>> Array datatype = float\n"); else printf("AN>> Array datatype = ?\n"); - if(npArray_type == NPY_FLOAT) + if (npArray_type == NPY_FLOAT) printf("AN>> Array type (%d) = f32\n", npArray_type); - if(PyArray_IS_C_CONTIGUOUS(npArray_ptr)) + if (PyArray_IS_C_CONTIGUOUS(npArray_ptr)) printf("AN>> Array is C-contiguous (row-major)\n"); - if(PyArray_IS_F_CONTIGUOUS(npArray_ptr)) + if (PyArray_IS_F_CONTIGUOUS(npArray_ptr)) printf("AN>> Array is Fortran-contiguous (column-major)\n"); - if(PyArray_ISBEHAVED_RO(npArray_ptr) || PyArray_ISBEHAVED(npArray_ptr)) + if (PyArray_ISBEHAVED_RO(npArray_ptr) || PyArray_ISBEHAVED(npArray_ptr)) printf("AN>> Array is well-behaved\n"); - if(npArray_flags & NPY_ARRAY_OWNDATA) + if (npArray_flags & NPY_ARRAY_OWNDATA) printf("AN>> Array owns its data\n"); else printf("AN>> Array does not own its data\n"); - for(int i_dim = 0; i_dim < npArray_ndim; ++i_dim) + for (int i_dim = 0; i_dim < npArray_ndim; ++i_dim) printf("AN>> Array-dim{%d} = %ld\n", i_dim, npArray_dims[i_dim]); // END DEBUG @@ -166,7 +164,10 @@ double* npyArray_to_CArray_WIP(PyObjectPtr& npy_array) // TODO: Verify array is 2d // void *PyArray_GETPTR2(PyArrayObject *obj, npy_intp i, npy_intp j) - // Quick, inline access to the element at the given coordinates in the ndarray, obj, which must have 2 dimensions (this is not checked). The corresponding i, j, coordinates can be any integer but will be interpreted as npy_intp. You may want to typecast the returned pointer to the data type of the ndarray. + // Quick, inline access to the element at the given coordinates in the ndarray, obj, which must + // have 2 dimensions (this is not checked). The corresponding i, j, coordinates can be any + // integer but will be interpreted as npy_intp. You may want to typecast the returned pointer to + // the data type of the ndarray. // element-by-element copy of the Numpy array data (always work) const std::size_t vsize = static_cast<std::size_t>(PyArray_SIZE(npArray_ptr)); @@ -175,18 +176,20 @@ double* npyArray_to_CArray_WIP(PyObjectPtr& npy_array) out1[i] = *reinterpret_cast<double*>(PyArray_GETPTR1(npArray_ptr, i)); } - // using bulk memcpy-like operation, but keep in mind that NumPy ndarrays may be mis-aligned for the data type, have non-native byte order, or other subtle attributes that make such copies less than desirable. + // using bulk memcpy-like operation, but keep in mind that NumPy ndarrays may be mis-aligned for + // the data type, have non-native byte order, or other subtle attributes that make such copies + // less than desirable. std::vector<double> out2(vsize); - std::memcpy(out2.data(), PyArray_DATA(npArray_ptr), - sizeof(double) * vsize); + std::memcpy(out2.data(), PyArray_DATA(npArray_ptr), sizeof(double) * vsize); // using either approach, out now contains a copy of the ndarray's data - double* npArray_data_ptr {reinterpret_cast<double*>(PyArray_DATA(npArray_ptr))}; - if(!npArray_data_ptr) { + double* npArray_data_ptr{reinterpret_cast<double*>(PyArray_DATA(npArray_ptr))}; + if (!npArray_data_ptr) { // checkError(); - throw std::runtime_error("PythonInterpreter: Cannot convert an invalid Numpy array to a C-Array (invalid data pointer)"); + throw std::runtime_error("PythonInterpreter: Cannot convert an invalid Numpy array to a " + "C-Array (invalid data pointer)"); } // TODO: Problem with the reference to numpy array!!! @@ -197,13 +200,12 @@ double* npyArray_to_CArray_WIP(PyObjectPtr& npy_array) Result<double*> NpyArray1D_to_C(PyObject* pyobject_ptr) { - if(!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { - return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: Cannot convert an invalid " - "Numpy array to a C-Array")}; + if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { + return {Status(Status::Type::Error, "PythonInterpreter::Numpy: Cannot convert an invalid " + "Numpy array to a C-Array")}; } - PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; + PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; PyArray_Descr* npArray_descr = PyArray_DTYPE(npArray_ptr); const int npArray_type = PyArray_TYPE(npArray_ptr); const int npArray_ndim = PyArray_NDIM(npArray_ptr); @@ -212,8 +214,8 @@ Result<double*> NpyArray1D_to_C(PyObject* pyobject_ptr) // character code indicating the data type const char npArray_dtype = npArray_descr->type; - //TODO: Enable type-casting for the Numpy array - if(npArray_type != NPY_DOUBLE) { + // TODO: Enable type-casting for the Numpy array + if (npArray_type != NPY_DOUBLE) { std::stringstream msg_ss; msg_ss << "PythonInterpreter::Numpy: " << "Expected a Numpy array of type _double_ " @@ -223,61 +225,59 @@ Result<double*> NpyArray1D_to_C(PyObject* pyobject_ptr) // DEBUG // type checking - if(PyDataType_ISUNSIGNED(npArray_descr)) + if (PyDataType_ISUNSIGNED(npArray_descr)) printf("AN>> Array datatype = uint\n"); - else if(PyDataType_ISSIGNED(npArray_descr)) + else if (PyDataType_ISSIGNED(npArray_descr)) printf("AN>> Array datatype = int\n"); - else if(PyDataType_ISFLOAT(npArray_descr)) + else if (PyDataType_ISFLOAT(npArray_descr)) printf("AN>> Array datatype = float\n"); else printf("AN>> Array datatype = ?\n"); - if(npArray_type == NPY_FLOAT) + if (npArray_type == NPY_FLOAT) printf("AN>> Array type (%d) = f32\n", npArray_type); - if(PyArray_IS_C_CONTIGUOUS(npArray_ptr)) + if (PyArray_IS_C_CONTIGUOUS(npArray_ptr)) printf("AN>> Array is C-contiguous (row-major)\n"); - if(PyArray_IS_F_CONTIGUOUS(npArray_ptr)) + if (PyArray_IS_F_CONTIGUOUS(npArray_ptr)) printf("AN>> Array is Fortran-contiguous (column-major)\n"); - if(PyArray_ISBEHAVED_RO(npArray_ptr) || PyArray_ISBEHAVED(npArray_ptr)) + if (PyArray_ISBEHAVED_RO(npArray_ptr) || PyArray_ISBEHAVED(npArray_ptr)) printf("AN>> Array is well-behaved\n"); - if(npArray_flags & NPY_ARRAY_OWNDATA) + if (npArray_flags & NPY_ARRAY_OWNDATA) printf("AN>> Array owns its data\n"); else printf("AN>> Array does not own its data\n"); - for(int i_dim = 0; i_dim < npArray_ndim; ++i_dim) + for (int i_dim = 0; i_dim < npArray_ndim; ++i_dim) printf("AN>> Array-dim{%d} = %ld\n", i_dim, npArray_dims[i_dim]); // END DEBUG // TODO: check if array is contiguous - double* npArray_data_ptr {reinterpret_cast<double*>(PyArray_DATA(npArray_ptr))}; - if(!npArray_data_ptr) { + double* npArray_data_ptr{reinterpret_cast<double*>(PyArray_DATA(npArray_ptr))}; + if (!npArray_data_ptr) { PythonInterpreter::checkError(); - return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: " - "Cannot convert an invalid Numpy array to a C-Array" - "(data pointer is invalid)")}; + return {Status(Status::Type::Error, "PythonInterpreter::Numpy: " + "Cannot convert an invalid Numpy array to a C-Array" + "(data pointer is invalid)")}; } return {std::move(npArray_data_ptr)}; } -PyObjectPtr call_pyfunction_npy_array( - PyObjectPtr& py_module, const std::string& fn_name, PyObjectPtr& npy_array) +PyObjectPtr call_pyfunction_npy_array(PyObjectPtr& py_module, const std::string& fn_name, + PyObjectPtr& npy_array) { // Call a Python function (from a given module) which takes a // single Numpy array as input and returns a Numpy array. if (!py_module.valid()) { - return {Status(Status::Type::Error, - "PythonInterpreter: Cannot call a function " - "from an invalid Python module")}; + return {Status(Status::Type::Error, "PythonInterpreter: Cannot call a function " + "from an invalid Python module")}; } PyObject* pFunc = PyObject_GetAttrString(py_module.ptr, fn_name.c_str()); @@ -289,14 +289,14 @@ PyObjectPtr call_pyfunction_npy_array( return {Status(Status::Type::Error, msg_ss.str())}; } - PyObject* pReturn = PyObject_CallFunctionObjArgs(pFunc, npy_array.ptr, - NULL); + PyObject* pReturn = PyObject_CallFunctionObjArgs(pFunc, npy_array.ptr, NULL); if (!PyArray_Check(pReturn)) { PythonInterpreter::checkError(); Py_DecRef(pFunc); std::stringstream msg_ss; msg_ss << "PythonInterpreter: Invalid return value from calling " - "the function '" << fn_name << "' (expected a Numpy array)"; + "the function '" + << fn_name << "' (expected a Numpy array)"; return {Status(Status::Type::Error, msg_ss.str())}; } @@ -304,7 +304,7 @@ PyObjectPtr call_pyfunction_npy_array( return {pReturn}; } -} // namespace +} // namespace //---------------------------------------- // Python stable ABI @@ -317,7 +317,6 @@ Status PythonInterpreter::initialize() msg_ss << "PythonInterpreter: Initialized Python - " << "version: " << Py_GetVersion(); return {Status::Type::Info, msg_ss.str()}; - } // if Python already initialized @@ -377,14 +376,12 @@ void PythonInterpreter::addPythonPath(const std::string& path) int PythonInterpreter::setPythonPath(const std::string& path) { // returns 0 on success, -1 on error - const int result = PySys_SetObject( - (char*)"path", PyUnicode_FromString(path.c_str())); + const int result = PySys_SetObject((char*)"path", PyUnicode_FromString(path.c_str())); return result; } // Python stable ABI -PyObjectPtr PythonInterpreter::import( - const std::string& pymodule_name, const std::string& path) +PyObjectPtr PythonInterpreter::import(const std::string& pymodule_name, const std::string& path) { addPythonPath(path); @@ -440,15 +437,14 @@ std::string PythonInterpreter::runtimeInfo() // Embedded Python details result << "Py_GetProgramName(): " << toString(Py_GetProgramName()) << "\n"; - result << "Py_GetProgramFullPath(): " << toString(Py_GetProgramFullPath()) - << "\n"; + result << "Py_GetProgramFullPath(): " << toString(Py_GetProgramFullPath()) << "\n"; result << "Py_GetPath(): " << toString(Py_GetPath()) << "\n"; result << "Py_GetPythonHome(): " << toString(Py_GetPythonHome()) << "\n"; // Runtime Python's sys.path PyObject* sysPath = PySys_GetObject((char*)"path"); - Result<std::vector<std::string>> content {toVectorString(sysPath)}; - if(content.valid()) { + Result<std::vector<std::string>> content{toVectorString(sysPath)}; + if (content.valid()) { result << "sys.path: "; for (auto s : content.val) result << s << ","; @@ -516,18 +512,18 @@ std::string PythonInterpreter::errorDescription(const std::string& title) //---------------------------------------- -Status PythonInterpreter::Numpy::initialize() { +Status PythonInterpreter::Numpy::initialize() +{ // initialize Python C API, if needed Status py_status{PythonInterpreter::initialize()}; - if(py_status.isError()) + if (py_status.isError()) return py_status; int res; _init_numpy(res); - switch(res) - { + switch (res) { case 1: return {Status::Type::Info, "PythonInterpreter: Initialized Numpy"}; case 2: @@ -545,16 +541,14 @@ bool PythonInterpreter::Numpy::isInitialized() //---------------------------------------- -Result<NpArrayDescr> PythonInterpreter::Numpy::arrayDescriptor( - PyObject* pyobject_ptr) +Result<NpArrayDescr> PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobject_ptr) { - if(!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { - return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: " - "Invalid Numpy array")}; + if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { + return {Status(Status::Type::Error, "PythonInterpreter::Numpy: " + "Invalid Numpy array")}; } - PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; + PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; PyArray_Descr* npArray_descr = PyArray_DTYPE(npArray_ptr); // const int npArray_type = PyArray_TYPE(npArray_ptr); const int npArray_ndim = PyArray_NDIM(npArray_ptr); @@ -568,49 +562,48 @@ Result<NpArrayDescr> PythonInterpreter::Numpy::arrayDescriptor( np_descr_out.n_dims = npArray_ndim; if (np_descr_out.n_dims > 0) { np_descr_out.dims.resize(npArray_ndim); - for(npy_intp i_d = 0; i_d < npArray_ndim; ++i_d) + for (npy_intp i_d = 0; i_d < npArray_ndim; ++i_d) np_descr_out.dims[i_d] = npArray_dims[i_d]; } np_descr_out.dtype = npArray_dtype; - if(PyArray_IS_C_CONTIGUOUS(npArray_ptr)) + if (PyArray_IS_C_CONTIGUOUS(npArray_ptr)) np_descr_out.C_contiguous = true; - if(PyArray_IS_F_CONTIGUOUS(npArray_ptr)) + if (PyArray_IS_F_CONTIGUOUS(npArray_ptr)) np_descr_out.F_contiguous = true; - if(PyArray_ISBEHAVED_RO(npArray_ptr)) + if (PyArray_ISBEHAVED_RO(npArray_ptr)) np_descr_out.wellbehaved = true; - if(npArray_flags & NPY_ARRAY_OWNDATA) + if (npArray_flags & NPY_ARRAY_OWNDATA) np_descr_out.owns_data = true; return {std::move(np_descr_out)}; } -Result<std::vector<double>> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) +Result<std::vector<double>> +PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) { - if(!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { - return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: Cannot convert an invalid " - "Numpy array to a C-Array")}; + if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { + return {Status(Status::Type::Error, "PythonInterpreter::Numpy: Cannot convert an invalid " + "Numpy array to a C-Array")}; } - PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; + PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; PyArray_Descr* npArray_descr = PyArray_DTYPE(npArray_ptr); const int npArray_type = PyArray_TYPE(npArray_ptr); const int npArray_ndim = PyArray_NDIM(npArray_ptr); npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); - const std::size_t npArray_size = static_cast<std::size_t> - (PyArray_SIZE(npArray_ptr)); + const std::size_t npArray_size = static_cast<std::size_t>(PyArray_SIZE(npArray_ptr)); const int npArray_flags = PyArray_FLAGS(npArray_ptr); // character code indicating the data type const char npArray_dtype = npArray_descr->type; // Numpy array type must be 2d - if(npArray_ndim != 2) { + if (npArray_ndim != 2) { std::stringstream msg_ss; msg_ss << "PythonInterpreter::Numpy: " << "Expected a Numpy 2d-array " @@ -619,7 +612,7 @@ Result<std::vector<double>> PythonInterpreter::Numpy::createVectorFromArray2D(Py } // Numpy array type must be numeric and real (eligible for casting to double) - if(!PyDataType_ISNUMBER(npArray_descr) || PyDataType_ISCOMPLEX(npArray_descr)) { + if (!PyDataType_ISNUMBER(npArray_descr) || PyDataType_ISCOMPLEX(npArray_descr)) { std::stringstream msg_ss; msg_ss << "PythonInterpreter::Numpy: " << "Expected a Numpy array of numeric type and real " @@ -632,8 +625,7 @@ Result<std::vector<double>> PythonInterpreter::Numpy::createVectorFromArray2D(Py const npy_intp rowIdxMax = npArray_dims[0], colIdxMax = npArray_dims[1]; // branch on type - switch(npArray_type) - { + switch (npArray_type) { // float case NPY_DOUBLE: _realArray2DToDouble<npy_double>(npArray_ptr, rowIdxMax, colIdxMax, data); @@ -698,8 +690,8 @@ Result<std::vector<double>> PythonInterpreter::Numpy::createVectorFromArray2D(Py } -PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC( - double* const c_array, const np_size_t dims[2]) +PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, + const np_size_t dims[2]) { if (!c_array) { return {Status(Status::Type::Error, @@ -717,8 +709,7 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC( npy_intp npDims[2] = {dims[0], dims[1]}; // create stand-alone Numpy array (float64) - PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 2, - npDims, NPY_DOUBLE); + PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 2, npDims, NPY_DOUBLE); if (!npArray_ptr) { checkError(); std::stringstream msg_ss; @@ -729,11 +720,9 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC( } // obtain pointer to data buffer of Numpy array - double* array_buffer = static_cast<double*>( - PyArray_DATA((PyArrayObject*)(npArray_ptr))); + double* array_buffer = static_cast<double*>(PyArray_DATA((PyArrayObject*)(npArray_ptr))); - for (np_size_t index = 0; index < size; ++index) - { + for (np_size_t index = 0; index < size; ++index) { *array_buffer = c_array[index]; ++array_buffer; } @@ -743,8 +732,8 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC( } -PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC( - double* const c_array, const np_size_t size) +PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, + const np_size_t size) { if (!c_array) { return {Status(Status::Type::Error, @@ -761,8 +750,7 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC( npy_intp npDims[1] = {size}; // create stand-alone Numpy array (float64) - PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 1, - npDims, NPY_DOUBLE); + PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 1, npDims, NPY_DOUBLE); if (!npArray_ptr) { checkError(); std::stringstream msg_ss; @@ -773,11 +761,9 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC( } // obtain pointer to data buffer of Numpy array - double* array_buffer = static_cast<double*>( - PyArray_DATA((PyArrayObject*)(npArray_ptr))); + double* array_buffer = static_cast<double*>(PyArray_DATA((PyArrayObject*)(npArray_ptr))); - for (np_size_t index = 0; index < size; ++index) - { + for (np_size_t index = 0; index < size; ++index) { *array_buffer = c_array[index]; ++array_buffer; } @@ -787,8 +773,7 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC( } -PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D( - double* const c_array, const np_size_t dims[2]) +PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const np_size_t dims[2]) { if (!c_array) { return {Status(Status::Type::Error, @@ -809,11 +794,10 @@ PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D( PyObject* npArray_ptr = PyArray_SimpleNewFromData( /* n_dims */ 2, npDims, NPY_DOUBLE, reinterpret_cast<void*>(c_array)); - if(!npArray_ptr || !PyArray_Check(npArray_ptr)) { + if (!npArray_ptr || !PyArray_Check(npArray_ptr)) { PythonInterpreter::checkError(); - return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: Cannot convert the given " - "C-Array to a Numpy 2D-array")}; + return {Status(Status::Type::Error, "PythonInterpreter::Numpy: Cannot convert the given " + "C-Array to a Numpy 2D-array")}; } // returns a _new_ reference; ie. caller is responsible for the ref-count @@ -833,9 +817,8 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio for (std::size_t d = 0; d < n_dims; ++d) { if (dimensions[d] < 2) { - return {Status(Status::Type::Error, - "Cannot make a Numpy with the given dimensions; " - "dimensions must be >= 2")}; + return {Status(Status::Type::Error, "Cannot make a Numpy with the given dimensions; " + "dimensions must be >= 2")}; } } @@ -845,8 +828,7 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio ndimsizes_numpy[d] = dimensions[d]; // creating standalone numpy array - PyObject* npyArray_ptr = PyArray_SimpleNew( - ndim_numpy, ndimsizes_numpy, NPY_DOUBLE); + PyObject* npyArray_ptr = PyArray_SimpleNew(ndim_numpy, ndimsizes_numpy, NPY_DOUBLE); delete[] ndimsizes_numpy; if (!npyArray_ptr) { @@ -865,11 +847,10 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio Result<double*> PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) { - PyArrayObject* npArray_ptr {reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; + PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; // get the pointer to data buffer of the Numpy array - double* data_ptr = static_cast<double*>( - PyArray_DATA((PyArrayObject*)npArray_ptr)); + double* data_ptr = static_cast<double*>(PyArray_DATA((PyArrayObject*)npArray_ptr)); if (!data_ptr) { checkError(); @@ -915,18 +896,17 @@ PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) } -PyObjectPtr PythonInterpreter::BornAgain::importScript( - const std::string& script, const std::string& path) +PyObjectPtr PythonInterpreter::BornAgain::importScript(const std::string& script, + const std::string& path) { - PyObjectPtr ba_pymodule {PythonInterpreter::BornAgain::import(path)}; + PyObjectPtr ba_pymodule{PythonInterpreter::BornAgain::import(path)}; // TODO: Check ba module PyObject* pCompiledFn = Py_CompileString(script.c_str(), "", Py_file_input); if (!pCompiledFn) { ba_pymodule.discard(); return {Status(Status::Type::Error, - PythonInterpreter::errorDescription( - "Cannot compile Python snippet"))}; + PythonInterpreter::errorDescription("Cannot compile Python snippet"))}; } // create a module @@ -935,23 +915,22 @@ PyObjectPtr PythonInterpreter::BornAgain::importScript( Py_DecRef(pCompiledFn); ba_pymodule.discard(); return {Status(Status::Type::Error, - PythonInterpreter::errorDescription( - "Cannot execute Python snippet"))}; + PythonInterpreter::errorDescription("Cannot execute Python snippet"))}; } return {tmpModule}; } -Result<std::vector<std::string>> PythonInterpreter::BornAgain::listOfFunctions( - const std::string& script, const std::string& path) +Result<std::vector<std::string>> +PythonInterpreter::BornAgain::listOfFunctions(const std::string& script, const std::string& path) { - PyObjectPtr tmpModule {PythonInterpreter::BornAgain::importScript(script, path)}; + PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; PyObject* pDict = PyModule_GetDict(tmpModule.ptr); if (!pDict) { return {Status(Status::Type::Error, "PythonInterpreter::BornAgain: " - "Cannot obtain the dictionary from the script module")}; + "Cannot obtain the dictionary from the script module")}; } PyObject *key, *value; @@ -959,7 +938,7 @@ Result<std::vector<std::string>> PythonInterpreter::BornAgain::listOfFunctions( std::vector<std::string> fn_names; while (PyDict_Next(pDict, &pos, &key, &value)) { if (PyCallable_Check(value)) { - std::string func_name {PythonInterpreter::pyStrtoString(key)}; + std::string func_name{PythonInterpreter::pyStrtoString(key)}; if (func_name.find("__") == std::string::npos) fn_names.push_back(func_name); } @@ -978,16 +957,14 @@ PyObjectPtr PythonInterpreter::Fabio::import() } -PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, - PyObjectPtr& fabio_module) +PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObjectPtr& fabio_module) { // load an image via calling `fabio.load` function which takes a // filename (Python string) and returns a Numpy array. if (!fabio_module.valid() || !PyModule_Check(fabio_module.ptr)) { - return {Status(Status::Type::Error, - "PythonInterpreter.fabio: Invalid Python module " - "(expected 'fabio' module)")}; + return {Status(Status::Type::Error, "PythonInterpreter.fabio: Invalid Python module " + "(expected 'fabio' module)")}; } PyObject* pFunc = PyObject_GetAttrString(fabio_module.ptr, (char*)"open"); diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index 26219081b53..f3423477f7c 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -1,9 +1,9 @@ #ifndef PYTHONINTRP_H #define PYTHONINTRP_H -#include <vector> -#include <string> #include "PyObjectPtr.h" +#include <string> +#include <vector> class MultiLayer; @@ -29,8 +29,7 @@ void addPythonPath(const std::string& path); int setPythonPath(const std::string& path); // Python stable ABI -PyObjectPtr import(const std::string& pymodule_name, - const std::string& path = ""); +PyObjectPtr import(const std::string& pymodule_name, const std::string& path = ""); // Python stable ABI void DecRef(PyObject* py_object); @@ -79,7 +78,7 @@ Result<std::vector<double>> createVectorFromArray2D(PyObject* pyobject_ptr); // The data is _not_ copied, and the Numpy array will _not_ own its data. PyObjectPtr CArrayAsNpy2D(double* const c_array, const np_size_t dims[2]); -} // namespace Numpy +} // namespace Numpy //-- BornAgain-related functionality namespace BornAgain { @@ -87,27 +86,25 @@ namespace BornAgain { PyObjectPtr import(const std::string& path = ""); // import a 'BornAgain' Python script -PyObjectPtr importScript(const std::string& script, - const std::string& path); +PyObjectPtr importScript(const std::string& script, const std::string& path); //! Returns list of functions defined in the script. //! @param script: Python script //! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH -Result<std::vector<std::string>> listOfFunctions( - const std::string& script, const std::string& path); +Result<std::vector<std::string>> listOfFunctions(const std::string& script, + const std::string& path); -} // namespace BornAgain +} // namespace BornAgain //-- Fabio-related functionality namespace Fabio { // import 'fabio' Python module PyObjectPtr import(); // call 'fabio.open' on a given file (needs Numpy) -PyObjectPtr open(const std::string& filename, - PyObjectPtr& fabio_module); +PyObjectPtr open(const std::string& filename, PyObjectPtr& fabio_module); } // namespace Fabio -} // namespace PythonInterpreter +} // namespace PythonInterpreter -#endif // PYTHONINTRP_H +#endif // PYTHONINTRP_H diff --git a/PyCore/Embed/Result.cpp b/PyCore/Embed/Result.cpp index 2b32315670f..c6561cd2a1d 100644 --- a/PyCore/Embed/Result.cpp +++ b/PyCore/Embed/Result.cpp @@ -1,8 +1,10 @@ #include "Result.h" -Status::Status(const Status::Type type, const std::string& message): - type{type}, - message{message} {} +Status::Status(const Status::Type type, const std::string& message) + : type{type} + , message{message} +{ +} bool Status::isSet() const { diff --git a/PyCore/Embed/Result.h b/PyCore/Embed/Result.h index 73f752fbfe0..a2940c211d8 100644 --- a/PyCore/Embed/Result.h +++ b/PyCore/Embed/Result.h @@ -1,22 +1,20 @@ #ifndef RESULT_H #define RESULT_H -#include <utility> // move #include <string> +#include <utility> // move // ======================================== // Indicator for the status of a result -struct Status -{ - enum class Type {None=-1, Info, Warning, Error}; +struct Status { + enum class Type { None = -1, Info, Warning, Error }; Type type = Type::None; // status message std::string message; - Status(const Status::Type type = Type::None, - const std::string& message = ""); + Status(const Status::Type type = Type::None, const std::string& message = ""); bool isSet() const; @@ -40,11 +38,9 @@ struct Status an exception should be raised or not. */ template <typename ResType> -class Result -{ +class Result { public: - using type = ResType; // stored result @@ -79,29 +75,36 @@ public: //-- Implementation of Result template <typename ResType> -Result<ResType>::Result(ResType&& result, - Status&& status): - val{std::move(result)}, - status{std::move(status)} {} +Result<ResType>::Result(ResType&& result, Status&& status) + : val{std::move(result)} + , status{std::move(status)} +{ +} template <typename ResType> -Result<ResType>::Result(ResType&& result): - Result(std::move(result), Status(Status::Type::Info)) {} +Result<ResType>::Result(ResType&& result) + : Result(std::move(result), Status(Status::Type::Info)) +{ +} template <typename ResType> -Result<ResType>::Result(Status&& status): - status{std::move(status)} {} +Result<ResType>::Result(Status&& status) + : status{std::move(status)} +{ +} template <typename ResType> -Result<ResType>::~Result() {} +Result<ResType>::~Result() +{ +} template <typename ResType> -Result<ResType>::Result(Result<ResType>&& other): - Result(std::move(other.val), std::move(other.status)) +Result<ResType>::Result(Result<ResType>&& other) + : Result(std::move(other.val), std::move(other.status)) { // reset the moved object other.reset(); @@ -128,4 +131,4 @@ bool Result<ResType>::isSet() const return (status.isSet()); } -#endif // RESULT_H +#endif // RESULT_H diff --git a/PyCore/Sample/Multilayer/ImportMultiLayer.cpp b/PyCore/Sample/Multilayer/ImportMultiLayer.cpp index 4da4157ac24..71e3661d8bb 100644 --- a/PyCore/Sample/Multilayer/ImportMultiLayer.cpp +++ b/PyCore/Sample/Multilayer/ImportMultiLayer.cpp @@ -1,21 +1,20 @@ #ifdef BORNAGAIN_PYTHON -#include "PyCore/Embed/PyCore.h" #include "ImportMultiLayer.h" +#include "PyCore/Embed/PyCore.h" +#include "PyCore/Embed/PythonInterpreter.h" // BornAgain::importScript #include "Sample/Multilayer/MultiLayer.h" -#include "PyCore/Embed/PythonInterpreter.h" // BornAgain::importScript // SWIG runtime access for creating a MultiLayer instance from Python #include "auto/Wrap/swig_runtime.h" -namespace PythonInterpreter -{ +namespace PythonInterpreter { -Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython( - const std::string& script, const std::string& functionName, - const std::string& path) +Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython(const std::string& script, + const std::string& functionName, + const std::string& path) { - PyObjectPtr tmpModule {PythonInterpreter::BornAgain::importScript(script, path)}; + PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; // locate the `get_simulation` function (it is an attribute of the module) PyObject* pAddFn = PyObject_GetAttrString(tmpModule.ptr, functionName.c_str()); @@ -36,8 +35,7 @@ Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython( std::stringstream msg_ss; msg_ss << "PythonInterpreter::BornAgain: " << "Cannot call the function '" << functionName << "'"; - return {Status(Status::Type::Error, - PythonInterpreter::errorDescription(msg_ss.str()))}; + return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg_ss.str()))}; } // Construct a C++ object from the Python object @@ -55,8 +53,7 @@ Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython( msg_ss << "PythonInterpreter::BornAgain: " << "SWIG failed to extract a 'MultiLayer' instance " << "via calling the function '" << functionName << "'"; - return {Status(Status::Type::Error, - PythonInterpreter::errorDescription(msg_ss.str()))}; + return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg_ss.str()))}; } MultiLayer* multilayer = reinterpret_cast<MultiLayer*>(argp1); @@ -68,6 +65,6 @@ Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython( return {std::move(result_ptr)}; } -} // namespace PythonInterpreter +} // namespace PythonInterpreter #endif // BORNAGAIN_PYTHON diff --git a/PyCore/Sample/Multilayer/ImportMultiLayer.h b/PyCore/Sample/Multilayer/ImportMultiLayer.h index ac1714992ab..4bf855213c4 100644 --- a/PyCore/Sample/Multilayer/ImportMultiLayer.h +++ b/PyCore/Sample/Multilayer/ImportMultiLayer.h @@ -1,23 +1,22 @@ #ifndef SWIG -#include <string> -#include <memory> // unique_ptr #include "PyCore/Embed/PyObjectDecl.h" #include "PyCore/Embed/Result.h" +#include <memory> // unique_ptr +#include <string> class MultiLayer; -namespace PythonInterpreter -{ +namespace PythonInterpreter { //! Creates a multi layer by running python code in embedded interpreter. //! @param script: Python script //! @param functionName: A function name in this script which produces a MultiLayer //! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH -Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython( - const std::string& script, const std::string& functionName, - const std::string& path = ""); +Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython(const std::string& script, + const std::string& functionName, + const std::string& path = ""); -} // namespace PythonInterpreter +} // namespace PythonInterpreter #endif // SWIG -- GitLab From df7ee30cf4a39164cbf02de16583884eb51a87aa Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 24 Apr 2023 15:35:01 +0200 Subject: [PATCH 30/85] PyCore/Embed/PythonInterpreter: rm unneeded functions and variables --- PyCore/Embed/PythonInterpreter.cpp | 203 +---------------------------- 1 file changed, 1 insertion(+), 202 deletions(-) diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index 74d6558405a..bcd277e974b 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -56,16 +56,6 @@ std::nullptr_t _init_numpy(int& status) return NULL; } - -//! Converts char to string. In the case of nullptr will return an empty string -std::string toString(const char* const c) -{ - if (c) - return c; - return ""; -} - - std::string toString(const wchar_t* const c) { if (!c) @@ -113,197 +103,6 @@ inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, npy_intp rowI } } - -double* npyArray_to_CArray_WIP(PyObjectPtr& npy_array) -{ - if (!npy_array.valid() || !PyArray_Check(npy_array.ptr)) { - throw std::runtime_error( - "PythonInterpreter: Cannot convert an invalid Numpy array to a C-Array"); - } - - PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(npy_array.ptr)}; - PyArray_Descr* npArray_descr = PyArray_DESCR(npArray_ptr); - const int npArray_type = PyArray_TYPE(npArray_ptr); - const int npArray_ndim = PyArray_NDIM(npArray_ptr); - npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); - const int npArray_flags = PyArray_FLAGS(npArray_ptr); - - // DEBUG - // type checking - if (PyDataType_ISUNSIGNED(npArray_descr)) - printf("AN>> Array datatype = uint\n"); - else if (PyDataType_ISSIGNED(npArray_descr)) - printf("AN>> Array datatype = int\n"); - else if (PyDataType_ISFLOAT(npArray_descr)) - printf("AN>> Array datatype = float\n"); - else - printf("AN>> Array datatype = ?\n"); - - if (npArray_type == NPY_FLOAT) - printf("AN>> Array type (%d) = f32\n", npArray_type); - - if (PyArray_IS_C_CONTIGUOUS(npArray_ptr)) - printf("AN>> Array is C-contiguous (row-major)\n"); - - if (PyArray_IS_F_CONTIGUOUS(npArray_ptr)) - printf("AN>> Array is Fortran-contiguous (column-major)\n"); - - if (PyArray_ISBEHAVED_RO(npArray_ptr) || PyArray_ISBEHAVED(npArray_ptr)) - printf("AN>> Array is well-behaved\n"); - - if (npArray_flags & NPY_ARRAY_OWNDATA) - printf("AN>> Array owns its data\n"); - else - printf("AN>> Array does not own its data\n"); - - for (int i_dim = 0; i_dim < npArray_ndim; ++i_dim) - printf("AN>> Array-dim{%d} = %ld\n", i_dim, npArray_dims[i_dim]); - - // END DEBUG - - // TODO: Verify array is 2d - - // void *PyArray_GETPTR2(PyArrayObject *obj, npy_intp i, npy_intp j) - // Quick, inline access to the element at the given coordinates in the ndarray, obj, which must - // have 2 dimensions (this is not checked). The corresponding i, j, coordinates can be any - // integer but will be interpreted as npy_intp. You may want to typecast the returned pointer to - // the data type of the ndarray. - - // element-by-element copy of the Numpy array data (always work) - const std::size_t vsize = static_cast<std::size_t>(PyArray_SIZE(npArray_ptr)); - std::vector<double> out1(vsize); - for (std::size_t i = 0; i < vsize; i++) { - out1[i] = *reinterpret_cast<double*>(PyArray_GETPTR1(npArray_ptr, i)); - } - - // using bulk memcpy-like operation, but keep in mind that NumPy ndarrays may be mis-aligned for - // the data type, have non-native byte order, or other subtle attributes that make such copies - // less than desirable. - - std::vector<double> out2(vsize); - std::memcpy(out2.data(), PyArray_DATA(npArray_ptr), sizeof(double) * vsize); - - // using either approach, out now contains a copy of the ndarray's data - - double* npArray_data_ptr{reinterpret_cast<double*>(PyArray_DATA(npArray_ptr))}; - if (!npArray_data_ptr) { - // checkError(); - throw std::runtime_error("PythonInterpreter: Cannot convert an invalid Numpy array to a " - "C-Array (invalid data pointer)"); - } - - // TODO: Problem with the reference to numpy array!!! - // returns a _new_ reference; ie. caller is responsible for the Python ref-count - return npArray_data_ptr; -} - - -Result<double*> NpyArray1D_to_C(PyObject* pyobject_ptr) -{ - if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { - return {Status(Status::Type::Error, "PythonInterpreter::Numpy: Cannot convert an invalid " - "Numpy array to a C-Array")}; - } - - PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; - PyArray_Descr* npArray_descr = PyArray_DTYPE(npArray_ptr); - const int npArray_type = PyArray_TYPE(npArray_ptr); - const int npArray_ndim = PyArray_NDIM(npArray_ptr); - npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); - const int npArray_flags = PyArray_FLAGS(npArray_ptr); - // character code indicating the data type - const char npArray_dtype = npArray_descr->type; - - // TODO: Enable type-casting for the Numpy array - if (npArray_type != NPY_DOUBLE) { - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::Numpy: " - << "Expected a Numpy array of type _double_ " - << "(given type '" << npArray_dtype << "')"; - return {Status(Status::Type::Error, msg_ss.str())}; - } - - // DEBUG - // type checking - if (PyDataType_ISUNSIGNED(npArray_descr)) - printf("AN>> Array datatype = uint\n"); - else if (PyDataType_ISSIGNED(npArray_descr)) - printf("AN>> Array datatype = int\n"); - else if (PyDataType_ISFLOAT(npArray_descr)) - printf("AN>> Array datatype = float\n"); - else - printf("AN>> Array datatype = ?\n"); - - if (npArray_type == NPY_FLOAT) - printf("AN>> Array type (%d) = f32\n", npArray_type); - - if (PyArray_IS_C_CONTIGUOUS(npArray_ptr)) - printf("AN>> Array is C-contiguous (row-major)\n"); - - if (PyArray_IS_F_CONTIGUOUS(npArray_ptr)) - printf("AN>> Array is Fortran-contiguous (column-major)\n"); - - if (PyArray_ISBEHAVED_RO(npArray_ptr) || PyArray_ISBEHAVED(npArray_ptr)) - printf("AN>> Array is well-behaved\n"); - - if (npArray_flags & NPY_ARRAY_OWNDATA) - printf("AN>> Array owns its data\n"); - else - printf("AN>> Array does not own its data\n"); - - for (int i_dim = 0; i_dim < npArray_ndim; ++i_dim) - printf("AN>> Array-dim{%d} = %ld\n", i_dim, npArray_dims[i_dim]); - - // END DEBUG - - // TODO: check if array is contiguous - double* npArray_data_ptr{reinterpret_cast<double*>(PyArray_DATA(npArray_ptr))}; - if (!npArray_data_ptr) { - PythonInterpreter::checkError(); - return {Status(Status::Type::Error, "PythonInterpreter::Numpy: " - "Cannot convert an invalid Numpy array to a C-Array" - "(data pointer is invalid)")}; - } - - return {std::move(npArray_data_ptr)}; -} - - -PyObjectPtr call_pyfunction_npy_array(PyObjectPtr& py_module, const std::string& fn_name, - PyObjectPtr& npy_array) -{ - // Call a Python function (from a given module) which takes a - // single Numpy array as input and returns a Numpy array. - - if (!py_module.valid()) { - return {Status(Status::Type::Error, "PythonInterpreter: Cannot call a function " - "from an invalid Python module")}; - } - - PyObject* pFunc = PyObject_GetAttrString(py_module.ptr, fn_name.c_str()); - if (!pFunc || !PyCallable_Check(pFunc)) { - PythonInterpreter::checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter: The function '" << fn_name << "' " - << "is not callable"; - return {Status(Status::Type::Error, msg_ss.str())}; - } - - PyObject* pReturn = PyObject_CallFunctionObjArgs(pFunc, npy_array.ptr, NULL); - if (!PyArray_Check(pReturn)) { - PythonInterpreter::checkError(); - Py_DecRef(pFunc); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter: Invalid return value from calling " - "the function '" - << fn_name << "' (expected a Numpy array)"; - return {Status(Status::Type::Error, msg_ss.str())}; - } - - // returns a _new_ reference; ie. caller is responsible for the ref-count - return {pReturn}; -} - } // namespace //---------------------------------------- @@ -598,7 +397,7 @@ PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) const int npArray_ndim = PyArray_NDIM(npArray_ptr); npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); const std::size_t npArray_size = static_cast<std::size_t>(PyArray_SIZE(npArray_ptr)); - const int npArray_flags = PyArray_FLAGS(npArray_ptr); + // const int npArray_flags = PyArray_FLAGS(npArray_ptr); // character code indicating the data type const char npArray_dtype = npArray_descr->type; -- GitLab From e0991fe73450ca56b85b7233de590d7f12ba5a88 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 21 Apr 2023 12:28:28 +0200 Subject: [PATCH 31/85] Turn off PyEmbedded tests [DEBUG] --- Tests/Unit/PyBinding/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/Unit/PyBinding/CMakeLists.txt b/Tests/Unit/PyBinding/CMakeLists.txt index a44ba90285a..23b3c6c0165 100644 --- a/Tests/Unit/PyBinding/CMakeLists.txt +++ b/Tests/Unit/PyBinding/CMakeLists.txt @@ -2,6 +2,8 @@ # BornAgain/Tests/Unit/PyBinding/CMakeLists.txt ############################################################################ +return() + include(GoogleTest) # provides gtest_discover_tests set(test TestPyBinding) -- GitLab From 584f515173fc6d812b67f49405cfb1ec3c47602a Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 13:57:09 +0200 Subject: [PATCH 32/85] Move 'PyCore/Sample/Multilayer/' to 'PyCore/Sample/' --- PyCore/Sample/{Multilayer => }/ImportMultiLayer.cpp | 0 PyCore/Sample/{Multilayer => }/ImportMultiLayer.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename PyCore/Sample/{Multilayer => }/ImportMultiLayer.cpp (100%) rename PyCore/Sample/{Multilayer => }/ImportMultiLayer.h (100%) diff --git a/PyCore/Sample/Multilayer/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp similarity index 100% rename from PyCore/Sample/Multilayer/ImportMultiLayer.cpp rename to PyCore/Sample/ImportMultiLayer.cpp diff --git a/PyCore/Sample/Multilayer/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h similarity index 100% rename from PyCore/Sample/Multilayer/ImportMultiLayer.h rename to PyCore/Sample/ImportMultiLayer.h -- GitLab From edda1da85e0a1db140cf8801667d2331518cfba6 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 14:07:42 +0200 Subject: [PATCH 33/85] Move Status implementation to PyObjectPtr --- PyCore/Embed/PyObjectPtr.cpp | 40 +++++++++++++++++++++++++++++++++++- PyCore/Embed/PyObjectPtr.h | 25 ++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/PyCore/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp index e7e908a1cc3..0a24fbbbbbc 100644 --- a/PyCore/Embed/PyObjectPtr.cpp +++ b/PyCore/Embed/PyObjectPtr.cpp @@ -1,10 +1,48 @@ #include "PyObjectPtr.h" #include "PythonInterpreter.h" -#include <exception> +#include <exception> // runtime_error #include <sstream> // stringstream #include <string> + +Status::Status(const Status::Type type, const std::string& message) + : type{type} + , message{message} +{ +} + +bool Status::isSet() const +{ + return (type != Status::Type::None); +} + + +bool Status::isOK() const +{ + return (type == Status::Type::Info); +} + + +bool Status::isWarning() const +{ + return (type == Status::Type::Warning); +} + + +bool Status::isError() const +{ + return (type == Status::Type::Error); +} + + +Status::operator bool() const +{ + return isOK() || isWarning(); +} + +//---------------------------------------- + PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr, const Status& status) : ptr{pyobject_ptr} , status{status} diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 41ffdb04eb3..e9de0100706 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -2,10 +2,31 @@ #define PYOBJECTPTR_H #include "PyObjectDecl.h" -#include "Result.h" // Status #include <vector> +#include <string> + +// Indicator for the status of a result +struct Status { + enum class Type { None = -1, Info, Warning, Error }; + Type type = Type::None; + + // status message + std::string message; + + Status(const Status::Type type = Type::None, const std::string& message = ""); + + bool isSet() const; + + bool isOK() const; + + bool isWarning() const; + + bool isError() const; + + // cast to boolean (true if result is valid) + operator bool() const; +}; -//======================================== // safe container for PyObjects /* -- GitLab From fb783379c1478f803b1217c1c737ef81a20f747e Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 14:12:04 +0200 Subject: [PATCH 34/85] rm Result module --- PyCore/Embed/Result.cpp | 38 ------------ PyCore/Embed/Result.h | 134 ---------------------------------------- 2 files changed, 172 deletions(-) delete mode 100644 PyCore/Embed/Result.cpp delete mode 100644 PyCore/Embed/Result.h diff --git a/PyCore/Embed/Result.cpp b/PyCore/Embed/Result.cpp deleted file mode 100644 index c6561cd2a1d..00000000000 --- a/PyCore/Embed/Result.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "Result.h" - -Status::Status(const Status::Type type, const std::string& message) - : type{type} - , message{message} -{ -} - -bool Status::isSet() const -{ - return (type != Status::Type::None); -} - - -bool Status::isOK() const -{ - return (type == Status::Type::Info); -} - - -bool Status::isWarning() const -{ - return (type == Status::Type::Warning); -} - - -bool Status::isError() const -{ - return (type == Status::Type::Error); -} - - -Status::operator bool() const -{ - return isOK() || isWarning(); -} - -//======================================== diff --git a/PyCore/Embed/Result.h b/PyCore/Embed/Result.h deleted file mode 100644 index a2940c211d8..00000000000 --- a/PyCore/Embed/Result.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef RESULT_H -#define RESULT_H - -#include <string> -#include <utility> // move - -// ======================================== - -// Indicator for the status of a result -struct Status { - enum class Type { None = -1, Info, Warning, Error }; - Type type = Type::None; - - // status message - std::string message; - - Status(const Status::Type type = Type::None, const std::string& message = ""); - - bool isSet() const; - - bool isOK() const; - - bool isWarning() const; - - bool isError() const; - - // cast to boolean (true if result is valid) - operator bool() const; -}; - -//======================================== - -// Container for Results which may fail -/* A `Result<T>` instance contains a value of type `T` (or none) and - a `Status` which denotes the status of the value (OK/Warning/Error). - This is used to avoid throwing exceptions in a _core_ module, - leaving the decision to the callers (or the higher interface) whether - an exception should be raised or not. -*/ -template <typename ResType> -class Result { - -public: - using type = ResType; - - // stored result - ResType val; - - // status of the PyObject - Status status; - - Result(ResType&& result, Status&& status); - - Result(ResType&& result); - - Result(Status&& status); - - ~Result(); - - // disallow copy constructor - Result(const Result&) = delete; - // disallow copy assignment - Result& operator=(const Result&) = delete; - // allow move constructor - Result(Result&& other); - // reset the container to the initial status - void reset(); - // check validity of the Result - bool valid() const; - // check if the Result is set - bool isSet() const; -}; - - -//-- Implementation of Result - -template <typename ResType> -Result<ResType>::Result(ResType&& result, Status&& status) - : val{std::move(result)} - , status{std::move(status)} -{ -} - - -template <typename ResType> -Result<ResType>::Result(ResType&& result) - : Result(std::move(result), Status(Status::Type::Info)) -{ -} - - -template <typename ResType> -Result<ResType>::Result(Status&& status) - : status{std::move(status)} -{ -} - - -template <typename ResType> -Result<ResType>::~Result() -{ -} - - -template <typename ResType> -Result<ResType>::Result(Result<ResType>&& other) - : Result(std::move(other.val), std::move(other.status)) -{ - // reset the moved object - other.reset(); -} - - -template <typename ResType> -void Result<ResType>::reset() -{ - status = Status(); -} - - -template <typename ResType> -bool Result<ResType>::valid() const -{ - return (status.isOK() || status.isWarning()); -} - - -template <typename ResType> -bool Result<ResType>::isSet() const -{ - return (status.isSet()); -} - -#endif // RESULT_H -- GitLab From 219ee2cf06d40aba6a0fbdd3f19cab92bfff528c Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 14:30:09 +0200 Subject: [PATCH 35/85] PyCore/Sample/ImportMultiLayer: avoid cyclic dependence on MultiLayer module; rm usage of Result class --- GUI/View/Project/PyImportAssistant.cpp | 9 ++++-- PyCore/Sample/ImportMultiLayer.cpp | 42 +++++++++++--------------- PyCore/Sample/ImportMultiLayer.h | 17 +++++++---- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index a6a0028e8de..fe8d60d4f34 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -103,9 +103,12 @@ QString readFile(const QString& fileName) std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QString& funcName) { QApplication::setOverrideCursor(Qt::WaitCursor); + void* result_ptr = nullptr; + PyObjectPtr result{PythonInterpreter::createMultiLayerFromPython( + snippet.toStdString(), funcName.toStdString(), bornagainDir(), result_ptr)}; - Result<std::unique_ptr<MultiLayer>> result{PythonInterpreter::createMultiLayerFromPython( - snippet.toStdString(), funcName.toStdString(), bornagainDir())}; + std::unique_ptr<MultiLayer> multilayer_ptr( + reinterpret_cast<MultiLayer*>(result_ptr)->clone()); if (!result.valid()) { QApplication::restoreOverrideCursor(); @@ -116,7 +119,7 @@ std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QStri QApplication::restoreOverrideCursor(); - return std::move(result.val); + return std::move(multilayer_ptr); } //! Lets user select a function name which generates a MultiLayer. diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index 71e3661d8bb..3572d83f0da 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -1,28 +1,28 @@ #ifdef BORNAGAIN_PYTHON -#include "ImportMultiLayer.h" #include "PyCore/Embed/PyCore.h" +#include "ImportMultiLayer.h" #include "PyCore/Embed/PythonInterpreter.h" // BornAgain::importScript -#include "Sample/Multilayer/MultiLayer.h" + // SWIG runtime access for creating a MultiLayer instance from Python #include "auto/Wrap/swig_runtime.h" namespace PythonInterpreter { -Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython(const std::string& script, - const std::string& functionName, - const std::string& path) +PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, + const std::string& script, + const std::string& functionName, + const std::string& path) { PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; // locate the `get_simulation` function (it is an attribute of the module) PyObject* pAddFn = PyObject_GetAttrString(tmpModule.ptr, functionName.c_str()); if (!pAddFn) { - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::BornAgain: " - << "Cannot locate the compiled function '" << functionName << "'"; - return {Status(Status::Type::Error, msg_ss.str())}; + std::string msg = "PythonInterpreter::BornAgain: " + "Cannot locate the compiled function '" + functionName + "'"; + return {Status(Status::Type::Error, msg)}; } // create a `MultiLayer` Python object via calling the function @@ -32,10 +32,9 @@ Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython(const std::string Py_DecRef(pAddFn); if (!instance) { - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::BornAgain: " - << "Cannot call the function '" << functionName << "'"; - return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg_ss.str()))}; + std::string msg = "PythonInterpreter::BornAgain: " + "Cannot call the function '" + functionName + "'"; + return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg))}; } // Construct a C++ object from the Python object @@ -49,20 +48,13 @@ Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython(const std::string const int res = SWIG_ConvertPtr(instance, &argp1, pTypeInfo, 0); if (!SWIG_IsOK(res)) { Py_DecRef(instance); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::BornAgain: " - << "SWIG failed to extract a 'MultiLayer' instance " - << "via calling the function '" << functionName << "'"; - return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg_ss.str()))}; + std::string msg = "PythonInterpreter::BornAgain: SWIG failed to extract a " + "'MultiLayer' instance via calling the function '" + functionName + "'"; + return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg))}; } - MultiLayer* multilayer = reinterpret_cast<MultiLayer*>(argp1); - std::unique_ptr<MultiLayer> result_ptr(multilayer->clone()); - - Py_DecRef(instance); - - // TODO: check if it is possible to move a unique_ptr - return {std::move(result_ptr)}; + multilayer_ptr = reinterpret_cast<void*>(argp1); + return {instance}; } } // namespace PythonInterpreter diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index 4bf855213c4..b7b62250920 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -1,8 +1,9 @@ +#ifndef BORNAGAIN_PYCORE_MULTILAYER_H +#define BORNAGAIN_PYCORE_MULTILAYER_H + #ifndef SWIG -#include "PyCore/Embed/PyObjectDecl.h" -#include "PyCore/Embed/Result.h" -#include <memory> // unique_ptr +#include "PyCore/Embed/PyObjectPtr.h" #include <string> class MultiLayer; @@ -13,10 +14,14 @@ namespace PythonInterpreter { //! @param script: Python script //! @param functionName: A function name in this script which produces a MultiLayer //! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH -Result<std::unique_ptr<MultiLayer>> createMultiLayerFromPython(const std::string& script, - const std::string& functionName, - const std::string& path = ""); +//! @param multilayer_ptr: a void pointer to the resulting MultiLayer instance in C++ +PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, + const std::string& script, + const std::string& functionName, + const std::string& path = ""); } // namespace PythonInterpreter #endif // SWIG + +#endif // BORNAGAIN_PYCORE_MULTILAYER_H -- GitLab From 06bf782e472ebcd067eeb7b472f20f3199098512 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 15:14:02 +0200 Subject: [PATCH 36/85] PythonInterpreter: rm usages of stringstream and Result class --- PyCore/Embed/PythonInterpreter.cpp | 270 ++++++++++++++--------------- PyCore/Embed/PythonInterpreter.h | 10 +- 2 files changed, 137 insertions(+), 143 deletions(-) diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index bcd277e974b..6cfd5ddc63e 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -22,13 +22,10 @@ References: #include <cstddef> // NULL #include <cstring> // memcpy #include <memory> // unique_ptr -#include <sstream> #include <string> #include <vector> - -#include <iostream> // DEBUG -//======================================== - +#include <stdexcept> // runtime_error +#include <iostream> // cerr namespace { //-- Auxiliary functions @@ -64,8 +61,15 @@ std::string toString(const wchar_t* const c) return std::string(wstr.begin(), wstr.end()); } +std::string toString(const char* const c) +{ + if (!c) + return ""; + return std::string(c); +} + //! Converts PyObject into vector of strings, if possible, or throws exception -Result<std::vector<std::string>> toVectorString(PyObject* py_object) +std::vector<std::string> toVectorString(PyObject* py_object) { std::vector<std::string> result; @@ -79,10 +83,14 @@ Result<std::vector<std::string>> toVectorString(PyObject* py_object) PyObject* value = PyList_GetItem(py_object, i); result.push_back(PythonInterpreter::pyStrtoString(value)); } - } else - return {Status(Status::Type::Error, "Cannnot convert the given Python object " - "to vector<string>.")}; - return {std::move(result)}; + } else { + throw(std::runtime_error( + PythonInterpreter::errorDescription( + "PythonInterpreter: Cannnot convert the given Python object " + "to vector<string>."))); + } + + return result; } @@ -109,19 +117,19 @@ inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, npy_intp rowI // Python stable ABI Status PythonInterpreter::initialize() { - std::stringstream msg_ss; + std::string msg; if (!Py_IsInitialized()) { Py_Initialize(); - msg_ss << "PythonInterpreter: Initialized Python - " - << "version: " << Py_GetVersion(); - return {Status::Type::Info, msg_ss.str()}; + return {Status::Type::Info, + "PythonInterpreter: Initialized Python - version: " + + toString(Py_GetVersion())}; } // if Python already initialized - msg_ss << "PythonInterpreter: Python already initialized - " - << "version: " << Py_GetVersion(); - return {Status::Type::Warning, msg_ss.str()}; + return {Status::Type::Warning, + "PythonInterpreter: Python already initialized - version: " + + toString(Py_GetVersion())}; } // Python stable ABI @@ -139,10 +147,8 @@ Status PythonInterpreter::finalize() // This is a no-op when called for a second time. Py_Finalize(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter: Finalized Python - " - << "version: " << Py_GetVersion(); - return {Status::Type::Info, msg_ss.str()}; + return {Status::Type::Info, + "PythonInterpreter: Finalized Python - version: " + toString(Py_GetVersion())}; } @@ -188,11 +194,9 @@ PyObjectPtr PythonInterpreter::import(const std::string& pymodule_name, const st PyObject* pymodule = PyImport_ImportModule(pymodule_name.c_str()); if (!pymodule || !PyModule_Check(pymodule)) { checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter: Cannot load Python module " - << "'" << pymodule_name << "' " - << "(given path = '" << path << "')"; - return PyObjectPtr(Status(Status::Type::Error, msg_ss.str())); + return {Status(Status::Type::Error, + errorDescription("PythonInterpreter: Cannot load Python module '" + + pymodule_name + "' (given path = '" + path + "')"))}; } // returns a _new_ reference; ie. caller is responsible for the ref-count @@ -222,10 +226,10 @@ std::string PythonInterpreter::pyStrtoString(PyObject* py_object) std::string PythonInterpreter::runtimeInfo() { - std::stringstream result; + std::string result; // Runtime environment - result << std::string(60, '=') << "\n"; + result = std::string(60, '=') + "\n"; /* DEBUG result << "PATH: " << BaseUtils::System::getenv("PATH") << "\n"; @@ -235,29 +239,28 @@ std::string PythonInterpreter::runtimeInfo() */ // Embedded Python details - result << "Py_GetProgramName(): " << toString(Py_GetProgramName()) << "\n"; - result << "Py_GetProgramFullPath(): " << toString(Py_GetProgramFullPath()) << "\n"; - result << "Py_GetPath(): " << toString(Py_GetPath()) << "\n"; - result << "Py_GetPythonHome(): " << toString(Py_GetPythonHome()) << "\n"; + result =+ "Py_GetProgramName(): " + toString(Py_GetProgramName()) + "\n"; + result =+ "Py_GetProgramFullPath(): " + toString(Py_GetProgramFullPath()) + "\n"; + result =+ "Py_GetPath(): " + toString(Py_GetPath()) + "\n"; + result =+ "Py_GetPythonHome(): " + toString(Py_GetPythonHome()) + "\n"; // Runtime Python's sys.path PyObject* sysPath = PySys_GetObject((char*)"path"); - Result<std::vector<std::string>> content{toVectorString(sysPath)}; - if (content.valid()) { - result << "sys.path: "; - for (auto s : content.val) - result << s << ","; - result << "\n"; + std::vector<std::string> content{toVectorString(sysPath)}; + result += "sys.path: "; + for (const std::string& s : content) { + result += s + ","; } + result += "\n"; - return result.str(); + return result; } // Attempt to retrieve Python stack trace // Ref: <https://stackoverflow.com/q/1796510> std::string PythonInterpreter::stackTrace() { - std::stringstream result; + std::string result; if (PyErr_Occurred()) { PyObject *ptype, *pvalue, *ptraceback, *pystr; @@ -265,7 +268,7 @@ std::string PythonInterpreter::stackTrace() PyErr_Fetch(&ptype, &pvalue, &ptraceback); pystr = PyObject_Str(pvalue); if (char* str = PyBytes_AsString(pystr)) - result << std::string(str) << "\n"; + result += std::string(str) + "\n"; Py_DecRef(pystr); PyObject* module_name = PyUnicode_FromString("traceback"); @@ -273,7 +276,7 @@ std::string PythonInterpreter::stackTrace() Py_DecRef(module_name); if (py_traceback_module) { - result << "\n"; + result += "\n"; PyObject* pyth_func = PyObject_GetAttrString(py_traceback_module, "format_exception"); if (pyth_func && PyCallable_Check(pyth_func)) { PyObject* pyth_val = @@ -282,31 +285,29 @@ std::string PythonInterpreter::stackTrace() if (pyth_val) { pystr = PyObject_Str(pyth_val); if (char* str = PyBytes_AsString(pystr)) - result << std::string(str); + result += std::string(str); Py_DecRef(pystr); Py_DecRef(pyth_val); } } - result << "\n"; + result += "\n"; } Py_DecRef(py_traceback_module); } - result << "\n"; - result << PythonInterpreter::runtimeInfo(); - result << "\n"; + result += "\n"; + result += PythonInterpreter::runtimeInfo(); + result += "\n"; - return result.str(); + return result; } std::string PythonInterpreter::errorDescription(const std::string& title) { - std::stringstream msg; - msg << title << "\n"; - msg << PythonInterpreter::stackTrace() << "\n"; - return msg.str(); + std::string msg = title + "\n" + PythonInterpreter::stackTrace() + "\n"; + return msg; } //---------------------------------------- @@ -340,11 +341,11 @@ bool PythonInterpreter::Numpy::isInitialized() //---------------------------------------- -Result<NpArrayDescr> PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobject_ptr) +NpArrayDescr PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobject_ptr) { if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { - return {Status(Status::Type::Error, "PythonInterpreter::Numpy: " - "Invalid Numpy array")}; + throw(std::runtime_error( + errorDescription("PythonInterpreter::Numpy: Invalid Numpy array"))); } PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; @@ -379,16 +380,17 @@ Result<NpArrayDescr> PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobjec if (npArray_flags & NPY_ARRAY_OWNDATA) np_descr_out.owns_data = true; - return {std::move(np_descr_out)}; + return np_descr_out; } -Result<std::vector<double>> +std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) { if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { - return {Status(Status::Type::Error, "PythonInterpreter::Numpy: Cannot convert an invalid " - "Numpy array to a C-Array")}; + throw(std::runtime_error( + errorDescription("PythonInterpreter::Numpy: Cannot convert an invalid " + "Numpy array to a C-Array"))); } PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; @@ -403,20 +405,20 @@ PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) // Numpy array type must be 2d if (npArray_ndim != 2) { - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::Numpy: " - << "Expected a Numpy 2d-array " - << "(given number of dimensions is " << npArray_ndim << ")"; - return {Status(Status::Type::Error, msg_ss.str())}; + std::string msg = "PythonInterpreter::Numpy: Expected a Numpy 2d-array " + "(given number of dimensions is " + std::to_string(npArray_ndim) + ")"; + throw(std::runtime_error( + errorDescription("PythonInterpreter::Numpy: Expected a Numpy 2d-array " + "(given number of dimensions is " + + std::to_string(npArray_ndim) + ")"))); } // Numpy array type must be numeric and real (eligible for casting to double) if (!PyDataType_ISNUMBER(npArray_descr) || PyDataType_ISCOMPLEX(npArray_descr)) { - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::Numpy: " - << "Expected a Numpy array of numeric type and real " - << "(given type '" << npArray_dtype << "')"; - return {Status(Status::Type::Error, msg_ss.str())}; + std::string msg = "PythonInterpreter::Numpy: " + "Expected a Numpy array of numeric type and real " + "(given type '" + std::to_string(npArray_dtype) + "')"; + throw(std::runtime_error(errorDescription(msg))); } std::vector<double> data(npArray_size); @@ -472,20 +474,14 @@ PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) #endif // _WIN32 default: - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::Numpy: " - << "Conversion of Numpy array of dtype '" << npArray_dtype << "' " - << "to vector<double> is not implemented"; - return {Status(Status::Type::Error, msg_ss.str())}; + throw(std::runtime_error( + errorDescription("PythonInterpreter::Numpy: " + "Conversion of Numpy array of dtype '" + + std::to_string(npArray_dtype) + "' " + "to vector<double> is not implemented"))); } - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::Numpy: " - << "Numpy 2d-array of dtype '" << npArray_dtype << "' " - << "and shape (" << npArray_dims[0] << "," << npArray_dims[1] << ") " - << "converted to vector<double> with size " << data.size(); - - return {std::move(data), Status(Status::Type::Info, msg_ss.str())}; + return data; } @@ -511,11 +507,10 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 2, npDims, NPY_DOUBLE); if (!npArray_ptr) { checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::Numpy: " - << "Cannot create a Numpy 1D-array from the " - << "given data (size = " << size << ")"; - return {Status(Status::Type::Error, msg_ss.str())}; + std::string msg = "PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from the " + "given data (size = " + std::to_string(size) + ")"; + return {Status(Status::Type::Error, errorDescription(msg))}; } // obtain pointer to data buffer of Numpy array @@ -542,8 +537,8 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, if (size < 1) { return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: " - "Cannot create a Numpy 1D-array from a data with size = 0")}; + errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a data with size = 0"))}; } npy_intp npDims[1] = {size}; @@ -552,11 +547,10 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 1, npDims, NPY_DOUBLE); if (!npArray_ptr) { checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::Numpy: " - << "Cannot create a Numpy 1D-array from the " - << "given data (size = " << size << ")"; - return {Status(Status::Type::Error, msg_ss.str())}; + std::string msg = "PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from the " + "given data (size = " + std::to_string(size) + ")"; + return {Status(Status::Type::Error, errorDescription(msg))}; } // obtain pointer to data buffer of Numpy array @@ -576,15 +570,15 @@ PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const { if (!c_array) { return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: " - "Cannot create a Numpy 2D-array from a null data pointer")}; + errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 2D-array from a null data pointer"))}; } const np_size_t size = dims[0] * dims[1]; if (size < 1) { return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: " - "Cannot create a Numpy 2D-array from a data with size = 0")}; + errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 2D-array from a data with size = 0"))}; } npy_intp npDims[2] = {dims[0], dims[1]}; @@ -595,8 +589,9 @@ PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const if (!npArray_ptr || !PyArray_Check(npArray_ptr)) { PythonInterpreter::checkError(); - return {Status(Status::Type::Error, "PythonInterpreter::Numpy: Cannot convert the given " - "C-Array to a Numpy 2D-array")}; + return {Status(Status::Type::Error, + errorDescription("PythonInterpreter::Numpy: Cannot convert the given " + "C-Array to a Numpy 2D-array"))}; } // returns a _new_ reference; ie. caller is responsible for the ref-count @@ -610,14 +605,15 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio const std::size_t n_dims = dimensions.size(); if (n_dims < 1) { return {Status(Status::Type::Error, - "Cannot make a Numpy with the given number of dimensions; " - "number of dimensions must be >= 1")}; + errorDescription("Cannot make a Numpy with the given number " + "of dimensions; number of dimensions must be >= 1"))}; } for (std::size_t d = 0; d < n_dims; ++d) { if (dimensions[d] < 2) { - return {Status(Status::Type::Error, "Cannot make a Numpy with the given dimensions; " - "dimensions must be >= 2")}; + return {Status(Status::Type::Error, + errorDescription("Cannot make a Numpy with the given dimensions; " + "dimensions must be >= 2"))}; } } @@ -632,18 +628,16 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio if (!npyArray_ptr) { checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::Numpy: " - << "Cannot create a Numpy " << n_dims << "D-array " - << "from the given data"; - return {Status(Status::Type::Error, msg_ss.str())}; + std::string msg = "PythonInterpreter::Numpy: Cannot create a Numpy " + + std::to_string(n_dims) + "D-array from the given data"; + return {Status(Status::Type::Error, errorDescription(msg))}; } return {npyArray_ptr}; } -Result<double*> PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) +double* PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) { PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; @@ -653,13 +647,12 @@ Result<double*> PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) if (!data_ptr) { checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter::Numpy: " - << "Numpy array has invalid data pointer"; - return {Status(Status::Type::Error, msg_ss.str())}; + throw(std::runtime_error( + errorDescription("PythonInterpreter::Numpy: " + "Numpy array has invalid data pointer"))); } - return {std::move(data_ptr)}; + return data_ptr; } //---------------------------------------- @@ -680,10 +673,9 @@ PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) if (!ba_pymodule || !PyModule_Check(ba_pymodule)) { checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter: Cannot load 'bornagain' Python module " - << "(given path = '" << path << "')"; - return {Status(Status::Type::Error, msg_ss.str())}; + std::string msg = "PythonInterpreter: Cannot load 'bornagain' Python module " + "(given path = '" + path + "')"; + return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg))}; } // restores single handler to make ctr-c alive. @@ -721,15 +713,16 @@ PyObjectPtr PythonInterpreter::BornAgain::importScript(const std::string& script } -Result<std::vector<std::string>> +std::vector<std::string> PythonInterpreter::BornAgain::listOfFunctions(const std::string& script, const std::string& path) { PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; PyObject* pDict = PyModule_GetDict(tmpModule.ptr); if (!pDict) { - return {Status(Status::Type::Error, "PythonInterpreter::BornAgain: " - "Cannot obtain the dictionary from the script module")}; + checkError(); + throw(std::runtime_error("PythonInterpreter::BornAgain: " + "Cannot obtain the dictionary from the script module")); } PyObject *key, *value; @@ -745,7 +738,7 @@ PythonInterpreter::BornAgain::listOfFunctions(const std::string& script, const s Py_DecRef(pDict); - return {std::move(fn_names)}; + return fn_names; } //---------------------------------------- @@ -762,25 +755,27 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject // filename (Python string) and returns a Numpy array. if (!fabio_module.valid() || !PyModule_Check(fabio_module.ptr)) { - return {Status(Status::Type::Error, "PythonInterpreter.fabio: Invalid Python module " - "(expected 'fabio' module)")}; + return {Status(Status::Type::Error, + errorDescription("PythonInterpreter.fabio: Invalid Python module " + "(expected 'fabio' module)"))}; } PyObject* pFunc = PyObject_GetAttrString(fabio_module.ptr, (char*)"open"); if (!pFunc || !PyCallable_Check(pFunc)) { PythonInterpreter::checkError(); return {Status(Status::Type::Error, - "PythonInterpreter.fabio: The function 'fabio.open' is not callable")}; + errorDescription("PythonInterpreter.fabio: " + "The function 'fabio.open' is not callable"))}; } // convert the filename to a Python unicode string PyObject* pFilename = PyUnicode_FromString(filename.c_str()); if (!pFilename) { PythonInterpreter::checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter.fabio: Filename '" << filename << "' " - << "cannot be converted to Python unicode string"; - return {Status(Status::Type::Error, msg_ss.str())}; + return {Status(Status::Type::Error, + errorDescription("PythonInterpreter.fabio: Filename '" + + filename + "' " + "cannot be converted to Python unicode string"))}; } // get the result of the call `fabio.open(<filename>)` @@ -788,10 +783,10 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject Py_DecRef(pFunc); if (!pResult_open) { PythonInterpreter::checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter.fabio: Invalid return value from " - << "calling the function 'fabio.open(\"" << filename << "\")'"; - return {Status(Status::Type::Error, msg_ss.str())}; + return {Status(Status::Type::Error, + errorDescription("PythonInterpreter.fabio: " + "Invalid return value from calling the function " + "'fabio.open(\"" + filename + "\")'"))}; } // get `result.data` (must be a Numpy array) @@ -799,11 +794,10 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject Py_DecRef(pResult_open); if (!npyArray_ptr || !PyArray_Check(npyArray_ptr)) { PythonInterpreter::checkError(); - std::stringstream msg_ss; - msg_ss << "PythonInterpreter.fabio: Invalid return value from " - << "calling the function 'fabio.open(\"" << filename << "\")' " - << "(expected a Numpy array)"; - return {Status(Status::Type::Error, msg_ss.str())}; + return {Status(Status::Type::Error, + errorDescription("PythonInterpreter.fabio: Invalid return value from " + "calling the function 'fabio.open(\"" + + filename + "\")' (expected a Numpy array)"))}; } // returns a _new_ reference; ie. caller is responsible for the ref-count diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index f3423477f7c..9ebf623b908 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -55,13 +55,13 @@ Status initialize(); bool isInitialized(); //! Obtains the descriptor of a Numpy array -Result<NpArrayDescr> arrayDescriptor(PyObject* pyobject_ptr); +NpArrayDescr arrayDescriptor(PyObject* pyobject_ptr); //! Creates an empty Numpy array of type `double` with the given dimensions PyObjectPtr arrayND(std::vector<std::size_t>& dimensions); //! Returns the pointer to the data buffer of a Numpy array of type `double` -Result<double*> getDataPtr(PyObject* pyobject_ptr); +double* getDataPtr(PyObject* pyobject_ptr); //! Creates a Numpy 1D-array from a contiguous C-array of type `double`. // Data will be copied. @@ -72,7 +72,7 @@ PyObjectPtr createArray1DfromC(double* const c_array, const np_size_t size); PyObjectPtr createArray2DfromC(double* const c_array, const np_size_t dims[2]); // !Creates a `vector<double>` from a Numpy 2D-array (data will be copied). -Result<std::vector<double>> createVectorFromArray2D(PyObject* pyobject_ptr); +std::vector<double> createVectorFromArray2D(PyObject* pyobject_ptr); // !Creates a 2D Numpy array from a contiguous C-array of type `double`. // The data is _not_ copied, and the Numpy array will _not_ own its data. @@ -91,8 +91,8 @@ PyObjectPtr importScript(const std::string& script, const std::string& path); //! Returns list of functions defined in the script. //! @param script: Python script //! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH -Result<std::vector<std::string>> listOfFunctions(const std::string& script, - const std::string& path); +std::vector<std::string> listOfFunctions(const std::string& script, + const std::string& path); } // namespace BornAgain -- GitLab From 3976648f86290c0fa3362a94ea0e141b3e7530df Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 8 May 2023 14:14:38 +0200 Subject: [PATCH 37/85] PythonInterpreter: add an explanation for "Python stable ABI" + cleanup --- PyCore/Embed/PythonInterpreter.cpp | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index 6cfd5ddc63e..98c35801436 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -28,7 +28,6 @@ References: #include <iostream> // cerr namespace { -//-- Auxiliary functions // thin wrapper to initialize Numpy std::nullptr_t _init_numpy(int& status) @@ -114,6 +113,9 @@ inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, npy_intp rowI } // namespace //---------------------------------------- +// NOTE: "Python stable ABI" denotes the functions which use _only_ the Python's stable ABI; +// see <https://docs.python.org/3/c-api/stable.html> + // Python stable ABI Status PythonInterpreter::initialize() { @@ -138,7 +140,6 @@ bool PythonInterpreter::isInitialized() return static_cast<bool>(Py_IsInitialized()); } - // Python stable ABI Status PythonInterpreter::finalize() { @@ -151,7 +152,6 @@ Status PythonInterpreter::finalize() "PythonInterpreter: Finalized Python - version: " + toString(Py_GetVersion())}; } - // Python stable ABI Status PythonInterpreter::checkError() { @@ -166,7 +166,6 @@ Status PythonInterpreter::checkError() return {Status::Type::Info, "No Python errors occured"}; } - // Python stable ABI void PythonInterpreter::addPythonPath(const std::string& path) { @@ -203,14 +202,12 @@ PyObjectPtr PythonInterpreter::import(const std::string& pymodule_name, const st return {pymodule}; } - // Python stable ABI void PythonInterpreter::DecRef(PyObject* py_object) { Py_XDECREF(py_object); } - std::string PythonInterpreter::pyStrtoString(PyObject* py_object) { std::string result; @@ -223,21 +220,12 @@ std::string PythonInterpreter::pyStrtoString(PyObject* py_object) return result; } - std::string PythonInterpreter::runtimeInfo() { std::string result; // Runtime environment result = std::string(60, '=') + "\n"; - /* DEBUG - - result << "PATH: " << BaseUtils::System::getenv("PATH") << "\n"; - result << "PYTHONPATH: " << BaseUtils::System::getenv("PYTHONPATH") << "\n"; - result << "PYTHONHOME: " << BaseUtils::System::getenv("PYTHONHOME") << "\n"; - - */ - // Embedded Python details result =+ "Py_GetProgramName(): " + toString(Py_GetProgramName()) + "\n"; result =+ "Py_GetProgramFullPath(): " + toString(Py_GetProgramFullPath()) + "\n"; @@ -303,7 +291,6 @@ std::string PythonInterpreter::stackTrace() return result; } - std::string PythonInterpreter::errorDescription(const std::string& title) { std::string msg = title + "\n" + PythonInterpreter::stackTrace() + "\n"; @@ -333,7 +320,6 @@ Status PythonInterpreter::Numpy::initialize() } } - bool PythonInterpreter::Numpy::isInitialized() { return static_cast<bool>(PyArray_API); @@ -383,7 +369,6 @@ NpArrayDescr PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobject_ptr) return np_descr_out; } - std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) { @@ -484,7 +469,6 @@ PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) return data; } - PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, const np_size_t dims[2]) { @@ -525,7 +509,6 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, return {npArray_ptr}; } - PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, const np_size_t size) { @@ -598,7 +581,6 @@ PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const return {npArray_ptr}; } - PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensions) { // descriptors for the array dimensions @@ -636,7 +618,6 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio return {npyArray_ptr}; } - double* PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) { @@ -654,7 +635,6 @@ double* PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) return data_ptr; } - //---------------------------------------- PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) @@ -686,7 +666,6 @@ PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) return {ba_pymodule}; } - PyObjectPtr PythonInterpreter::BornAgain::importScript(const std::string& script, const std::string& path) { @@ -740,7 +719,6 @@ PythonInterpreter::BornAgain::listOfFunctions(const std::string& script, const s return fn_names; } - //---------------------------------------- PyObjectPtr PythonInterpreter::Fabio::import() -- GitLab From 921f17f1227e6c9d51aaf3ede98bdb2b715c2bd2 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 16:15:04 +0200 Subject: [PATCH 38/85] Datafield::npArray: rm usage of Result class --- Device/Data/Datafield.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Device/Data/Datafield.cpp b/Device/Data/Datafield.cpp index 336b82c9d57..58e88ebea20 100644 --- a/Device/Data/Datafield.cpp +++ b/Device/Data/Datafield.cpp @@ -228,12 +228,12 @@ PyObject* Datafield::npArray() const return nullptr; // get the pointer to the data buffer of the array (assumed to be C-contiguous) - Result<double*> data{PythonInterpreter::Numpy::getDataPtr(pyarray.ptr)}; + double* data{PythonInterpreter::Numpy::getDataPtr(pyarray.ptr)}; - if (!data.valid()) + if (!data) return nullptr; - double* array_buffer = data.val; + double* array_buffer = data; // filling numpy array with output_data if (rank() == 2) { -- GitLab From 21f555fadda7e7ad3fb9e03c8a2f19c224d6b0dd Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 17:19:34 +0200 Subject: [PATCH 39/85] Datafield::npArray: add assertion to disallow rank() > 2 --- Device/Data/Datafield.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Device/Data/Datafield.cpp b/Device/Data/Datafield.cpp index 58e88ebea20..f9f4aa00b3b 100644 --- a/Device/Data/Datafield.cpp +++ b/Device/Data/Datafield.cpp @@ -214,6 +214,9 @@ PyObject* Datafield::npArray() const Status init{PythonInterpreter::Numpy::initialize()}; if (init.isError()) return nullptr; + + ASSERT(rank() <= 2); + std::vector<size_t> dimensions; for (size_t i = 0; i < rank(); i++) dimensions.push_back(axis(i).size()); @@ -246,8 +249,6 @@ PyObject* Datafield::npArray() const } else if (rank() == 1) { for (size_t i = 0; i < size(); ++i) *array_buffer++ = (*this)[i]; - } else { - return nullptr; } // returns a _new_ reference; ie. caller is responsible for the ref-count -- GitLab From ca8eb9e4e2f8bde2650d29981ca60cfd38e61f78 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 16:15:12 +0200 Subject: [PATCH 40/85] PyImportAssistant: rm usage of Result class fx PyImportAssistant --- GUI/View/Project/PyImportAssistant.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index fe8d60d4f34..a1308c8c465 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -30,7 +30,7 @@ #include "GUI/View/Project/ProjectManager.h" #include "GUI/View/Tool/Globals.h" #include "PyCore/Embed/PythonInterpreter.h" // listOfFunctions -#include "PyCore/Sample/Multilayer/ImportMultiLayer.h" // createMultiLayerFromPython +#include "PyCore/Sample/ImportMultiLayer.h" // createMultiLayerFromPython #include "Sample/Multilayer/MultiLayer.h" #include <QApplication> #include <QFileDialog> @@ -104,8 +104,8 @@ std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QStri { QApplication::setOverrideCursor(Qt::WaitCursor); void* result_ptr = nullptr; - PyObjectPtr result{PythonInterpreter::createMultiLayerFromPython( - snippet.toStdString(), funcName.toStdString(), bornagainDir(), result_ptr)}; + PyObjectPtr result{PythonInterpreter::createMultiLayerFromPython(result_ptr, + snippet.toStdString(), funcName.toStdString(), bornagainDir())}; std::unique_ptr<MultiLayer> multilayer_ptr( reinterpret_cast<MultiLayer*>(result_ptr)->clone()); @@ -119,7 +119,7 @@ std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QStri QApplication::restoreOverrideCursor(); - return std::move(multilayer_ptr); + return multilayer_ptr; } //! Lets user select a function name which generates a MultiLayer. @@ -154,19 +154,18 @@ QString getPySampleFunctionName(const QString& snippet) { QApplication::setOverrideCursor(Qt::WaitCursor); - Result<std::vector<std::string>> funcs_res{ + std::vector<std::string> funcs_res{ PythonInterpreter::BornAgain::listOfFunctions(snippet.toStdString(), bornagainDir())}; - if (!funcs_res.valid()) { + if (funcs_res.empty()) { QApplication::restoreOverrideCursor(); QString message("Exception thrown while acquiring functions from Python code.\n\n"); - QString details = QString::fromStdString(funcs_res.status.message); - DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, details).exec(); + DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, "").exec(); return ""; } QApplication::restoreOverrideCursor(); - QStringList funcList{GUI::Util::String::fromStdStrings(funcs_res.val)}; + QStringList funcList{GUI::Util::String::fromStdStrings(funcs_res)}; return selectPySampleFunction(funcList); } -- GitLab From 791437d9ec91894c6d60b2b2c763a56c44e43a45 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 15:12:55 +0200 Subject: [PATCH 41/85] PyObjectPtr: Minor improvements --- PyCore/Embed/PyObjectPtr.cpp | 3 +-- PyCore/Embed/PyObjectPtr.h | 10 ++-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/PyCore/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp index 0a24fbbbbbc..d951b44ecf7 100644 --- a/PyCore/Embed/PyObjectPtr.cpp +++ b/PyCore/Embed/PyObjectPtr.cpp @@ -1,8 +1,7 @@ #include "PyObjectPtr.h" #include "PythonInterpreter.h" -#include <exception> // runtime_error -#include <sstream> // stringstream +#include <stdexcept> // runtime_error #include <string> diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index e9de0100706..8313279a4b9 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -45,19 +45,13 @@ public: Status status; PyObjectPtr(PyObject* pyobject_ptr, const Status& status); - PyObjectPtr(PyObject* pyobject_ptr); - PyObjectPtr(const Status& status); - ~PyObjectPtr(); - - // disallow copy constructor PyObjectPtr(const PyObjectPtr&) = delete; - // disallow copy assignment - PyObjectPtr& operator=(const PyObjectPtr&) = delete; - // allow move constructor PyObjectPtr(PyObjectPtr&& other); + PyObjectPtr& operator=(const PyObjectPtr&) = delete; + // reset the container to the initial status (does _not_ release the Python resource) void reset(); // reset the container to the initial status and return the PyObject pointer (does _not_ release -- GitLab From e713f4b718fc00af6f3f7a34c6f2eeb59e4a580a Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 17:28:45 +0200 Subject: [PATCH 42/85] PyObjectPtr: define Status as a class, instead of a struct --- PyCore/Embed/PyObjectPtr.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 8313279a4b9..49fb7af3e8d 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -6,7 +6,8 @@ #include <string> // Indicator for the status of a result -struct Status { +class Status { +public: enum class Type { None = -1, Info, Warning, Error }; Type type = Type::None; -- GitLab From 3d62c93209e05ae41db6102e9562a56ba528bc85 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 17:25:48 +0200 Subject: [PATCH 43/85] PyObjectPtr: rename NpArrayDescr to NumpyArrayDescriptor --- PyCore/Embed/PyObjectPtr.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 49fb7af3e8d..a17ccce0aa6 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -68,10 +68,10 @@ public: // Numpy array descriptor -struct NpArrayDescr { +struct NumpyArrayDescriptor { // Numpy array descriptors - bool C_contiguous = false; - bool F_contiguous = false; + bool C_contiguous = false; // C-contiguous array flag + bool F_contiguous = false; // FORTRAN-contiguous array flag // whether the data area of arr is aligned and in machine byte-order bool wellbehaved = false; // number of dimensions -- GitLab From 19b614ad302054f35b9cdf0722e06410f9b469e5 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 17:26:11 +0200 Subject: [PATCH 44/85] PythonInterpreter: rename NpArrayDescr to NumpyArrayDescriptor --- PyCore/Embed/PythonInterpreter.cpp | 4 ++-- PyCore/Embed/PythonInterpreter.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index 98c35801436..69228a73236 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -327,7 +327,7 @@ bool PythonInterpreter::Numpy::isInitialized() //---------------------------------------- -NpArrayDescr PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobject_ptr) +NumpyArrayDescriptor PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobject_ptr) { if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { throw(std::runtime_error( @@ -344,7 +344,7 @@ NpArrayDescr PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobject_ptr) const char npArray_dtype = npArray_descr->type; - NpArrayDescr np_descr_out; + NumpyArrayDescriptor np_descr_out; np_descr_out.n_dims = npArray_ndim; if (np_descr_out.n_dims > 0) { np_descr_out.dims.resize(npArray_ndim); diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index 9ebf623b908..5d1c241f70c 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -55,7 +55,7 @@ Status initialize(); bool isInitialized(); //! Obtains the descriptor of a Numpy array -NpArrayDescr arrayDescriptor(PyObject* pyobject_ptr); +NumpyArrayDescriptor arrayDescriptor(PyObject* pyobject_ptr); //! Creates an empty Numpy array of type `double` with the given dimensions PyObjectPtr arrayND(std::vector<std::size_t>& dimensions); -- GitLab From 04a8c62f35557f92e50911d2e080579e3d70cb29 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Fri, 28 Apr 2023 17:30:55 +0200 Subject: [PATCH 45/85] PyObjectDecl: rm USER_API --- PyCore/Embed/PyObjectDecl.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/PyCore/Embed/PyObjectDecl.h b/PyCore/Embed/PyObjectDecl.h index dfdd0e71c31..12b12be4428 100644 --- a/PyCore/Embed/PyObjectDecl.h +++ b/PyCore/Embed/PyObjectDecl.h @@ -1,7 +1,5 @@ // forward declarations needed for using `PyObject` -#ifndef USER_API - #ifndef PYOBJECT_H #define PYOBJECT_H @@ -13,4 +11,3 @@ typedef _object PyObject; typedef long int np_size_t; // size type used in Numpy #endif // PYOBJECT_H -#endif // USER_API -- GitLab From 996f35490de137a63dab32da6c188d2000f94611 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 8 May 2023 11:50:20 +0200 Subject: [PATCH 46/85] PyCore: add the standard initial comment blocks to all header files --- PyCore/Embed/PyObjectDecl.h | 20 ++++++++++++++++---- PyCore/Embed/PyObjectPtr.h | 20 +++++++++++++++++--- PyCore/Embed/PythonInterpreter.h | 20 +++++++++++++++++--- PyCore/Sample/ImportMultiLayer.h | 20 +++++++++++++++++--- 4 files changed, 67 insertions(+), 13 deletions(-) diff --git a/PyCore/Embed/PyObjectDecl.h b/PyCore/Embed/PyObjectDecl.h index 12b12be4428..715cc1aa599 100644 --- a/PyCore/Embed/PyObjectDecl.h +++ b/PyCore/Embed/PyObjectDecl.h @@ -1,7 +1,19 @@ -// forward declarations needed for using `PyObject` +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file PyCore/Embed/PyObjectDecl.h +//! @brief Forward declarations needed for using `PyObject`. +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ -#ifndef PYOBJECT_H -#define PYOBJECT_H +#ifndef BORNAGAIN_PYCORE_EMBED_PYOBJECTDECL_H +#define BORNAGAIN_PYCORE_EMBED_PYOBJECTDECL_H #ifndef PyObject_HEAD struct _object; @@ -10,4 +22,4 @@ typedef _object PyObject; typedef long int np_size_t; // size type used in Numpy -#endif // PYOBJECT_H +#endif // BORNAGAIN_PYCORE_EMBED_PYOBJECTDECL_H diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index a17ccce0aa6..8e7d0076c82 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -1,5 +1,19 @@ -#ifndef PYOBJECTPTR_H -#define PYOBJECTPTR_H +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file PyCore/Embed/PyObjectPtr.h +//! @brief Defines classes PyObjectPtr and Status. +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifndef BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H +#define BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H #include "PyObjectDecl.h" #include <vector> @@ -84,4 +98,4 @@ struct NumpyArrayDescriptor { bool owns_data = false; }; -#endif // PYOBJECTPTR_H +#endif // BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index 5d1c241f70c..0a4a2fc5249 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -1,5 +1,19 @@ -#ifndef PYTHONINTRP_H -#define PYTHONINTRP_H +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file PyCore/Embed/PythonInterpreter.h +//! @brief Definition of functions and classes to expose Python-interpreter functionality to C++. +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifndef BORNAGAIN_PYCORE_EMBED_PYTHONINTERPRETER_H +#define BORNAGAIN_PYCORE_EMBED_PYTHONINTERPRETER_H #include "PyObjectPtr.h" #include <string> @@ -107,4 +121,4 @@ PyObjectPtr open(const std::string& filename, PyObjectPtr& fabio_module); } // namespace PythonInterpreter -#endif // PYTHONINTRP_H +#endif // BORNAGAIN_PYCORE_EMBED_PYTHONINTERPRETER_H diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index b7b62250920..57d3e4a9120 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -1,5 +1,19 @@ -#ifndef BORNAGAIN_PYCORE_MULTILAYER_H -#define BORNAGAIN_PYCORE_MULTILAYER_H +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file PyCore/Sample/ImportMultiLayer.h +//! @brief Forward declaration for createMultiLayerFromPython. +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifndef BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H +#define BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H #ifndef SWIG @@ -24,4 +38,4 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, #endif // SWIG -#endif // BORNAGAIN_PYCORE_MULTILAYER_H +#endif // BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H -- GitLab From 8651f1947819dff716799fee2c0a3753add35486 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 8 May 2023 12:19:18 +0200 Subject: [PATCH 47/85] PyCore: use Doxygen style for comments --- PyCore/Embed/PyCore.h | 7 ++-- PyCore/Embed/PyObjectDecl.h | 4 ++- PyCore/Embed/PyObjectPtr.h | 56 ++++++++++++++++---------------- PyCore/Embed/PythonInterpreter.h | 30 ++++++++--------- PyCore/Sample/ImportMultiLayer.h | 12 ++++--- 5 files changed, 56 insertions(+), 53 deletions(-) diff --git a/PyCore/Embed/PyCore.h b/PyCore/Embed/PyCore.h index 42027dff657..211857da2bf 100644 --- a/PyCore/Embed/PyCore.h +++ b/PyCore/Embed/PyCore.h @@ -36,11 +36,12 @@ #ifdef INCLUDE_NUMPY #ifdef BORNAGAIN_PYTHON_API_SWIG -/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` and `NO_IMPORT_ARRAY` one can use - the Numpy Array API in multiple files for a single extension module. -*/ + +// NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` and `NO_IMPORT_ARRAY` one can use +// the Numpy Array API in multiple files for a single extension module. #define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY #define NO_IMPORT_ARRAY + #endif // BORNAGAIN_PYTHON_API_SWIG #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION // to prevent spurious warning diff --git a/PyCore/Embed/PyObjectDecl.h b/PyCore/Embed/PyObjectDecl.h index 715cc1aa599..c1af8b2a02d 100644 --- a/PyCore/Embed/PyObjectDecl.h +++ b/PyCore/Embed/PyObjectDecl.h @@ -16,9 +16,11 @@ #define BORNAGAIN_PYCORE_EMBED_PYOBJECTDECL_H #ifndef PyObject_HEAD + struct _object; typedef _object PyObject; -#endif + +#endif // PyObject_HEAD typedef long int np_size_t; // size type used in Numpy diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 8e7d0076c82..a8c5a3f1ecc 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -19,13 +19,13 @@ #include <vector> #include <string> -// Indicator for the status of a result +//! Indicator for the status of a result class Status { public: enum class Type { None = -1, Info, Warning, Error }; Type type = Type::None; - // status message + //! Status message std::string message; Status(const Status::Type type = Type::None, const std::string& message = ""); @@ -38,25 +38,23 @@ public: bool isError() const; - // cast to boolean (true if result is valid) + //! Cast the validity status to boolean (true if result is valid) operator bool() const; }; -// safe container for PyObjects -/* -The class `PyObjectPtr` contains a `PyObject*` (or `NULL`) and a `Status` which denotes the status -of the value (OK/Warning/Error). Decrementing Python reference-count is performed automatically when -a `PyObjectPtr` expires. -*/ - +//! Safe container for PyObjects +//! The class `PyObjectPtr` contains a `PyObject*` (or `NULL`) and +//! a `Status` which denotes the status of the value (OK/Warning/Error). +//! Decrementing Python reference-count is performed automatically when +//! a `PyObjectPtr` expires. class PyObjectPtr { public: - // raw pointer to the PyObject + //! Raw pointer to the PyObject PyObject* ptr = nullptr; - // status of the PyObject + //! Status of the PyObject Status status; PyObjectPtr(PyObject* pyobject_ptr, const Status& status); @@ -67,34 +65,36 @@ public: PyObjectPtr(PyObjectPtr&& other); PyObjectPtr& operator=(const PyObjectPtr&) = delete; - // reset the container to the initial status (does _not_ release the Python resource) + //! Resets the container to the initial status (does _not_ release the Python resource) void reset(); - // reset the container to the initial status and return the PyObject pointer (does _not_ release - // the Python resource) + //! Resets the container to the initial status and return the PyObject pointer + //! (does _not_ release the Python resource) PyObject* release(); - // discards the Python resource (decrements the Python reference) + //! Discards the Python resource (decrements the Python reference) void discard(); - // check validity of the PyObjectPtr + //! Check the validity of the PyObjectPtr bool valid() const; - // check if the PyObjectPtr is set + //! Checks whether the PyObjectPtr is set bool isSet() const; }; -// Numpy array descriptor -struct NumpyArrayDescriptor { - // Numpy array descriptors - bool C_contiguous = false; // C-contiguous array flag - bool F_contiguous = false; // FORTRAN-contiguous array flag - // whether the data area of arr is aligned and in machine byte-order +//! Numpy array descriptors +struct NumpyArrayDescriptor +{ + //! C-contiguous array flag + bool C_contiguous = false; + //! FORTRAN-contiguous array flag + bool F_contiguous = false; + //! Flag to indicate if the data area of the Numpy array is aligned and in machine byte-order bool wellbehaved = false; - // number of dimensions + //! Number of dimensions int n_dims = 0; - // sizes of dimensions + //! Sizes of dimensions std::vector<np_size_t> dims; - // character code indicating the data type + //! Character code indicating the data type char dtype = '\0'; - // whether Numpy array owns its data + //! Flag to indicate whether the Numpy array owns its data bool owns_data = false; }; diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index 0a4a2fc5249..e98a1ae63cc 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -21,7 +21,6 @@ class MultiLayer; -// Embedded Python interpreter namespace PythonInterpreter { // Python stable ABI @@ -62,10 +61,10 @@ std::string errorDescription(const std::string& title = ""); //-- Numpy-related functionality namespace Numpy { -// initialize Numpy +//! Initializes Numpy Status initialize(); -// Python stable ABI +//! Checks if Numpy is initialized bool isInitialized(); //! Obtains the descriptor of a Numpy array @@ -81,40 +80,39 @@ double* getDataPtr(PyObject* pyobject_ptr); // Data will be copied. PyObjectPtr createArray1DfromC(double* const c_array, const np_size_t size); -// !Creates a Numpy 2D-array from a contiguous C-array of type `double`. -// Data will be copied. +//! Creates a Numpy 2D-array from a contiguous C-array of type `double`. +//! Data will be copied. PyObjectPtr createArray2DfromC(double* const c_array, const np_size_t dims[2]); -// !Creates a `vector<double>` from a Numpy 2D-array (data will be copied). +//! Creates a `vector<double>` from a Numpy 2D-array (data will be copied). std::vector<double> createVectorFromArray2D(PyObject* pyobject_ptr); -// !Creates a 2D Numpy array from a contiguous C-array of type `double`. -// The data is _not_ copied, and the Numpy array will _not_ own its data. +//! Creates a 2D Numpy array from a contiguous C-array of type `double`. +//! The data is _not_ copied, and the Numpy array will _not_ own its data. PyObjectPtr CArrayAsNpy2D(double* const c_array, const np_size_t dims[2]); } // namespace Numpy -//-- BornAgain-related functionality +// BornAgain-related functionality namespace BornAgain { //! Imports BornAgain from given location. If path is empty, tries to rely on PYTHONPATH. PyObjectPtr import(const std::string& path = ""); -// import a 'BornAgain' Python script +//! Imports a 'BornAgain' Python script PyObjectPtr importScript(const std::string& script, const std::string& path); -//! Returns list of functions defined in the script. -//! @param script: Python script -//! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH +//! Returns list of functions defined in the script, given a script filename and +//! a path to import BornAgain library (if empty, relies on `PYTHONPATH`). std::vector<std::string> listOfFunctions(const std::string& script, const std::string& path); } // namespace BornAgain -//-- Fabio-related functionality +// Fabio-related functionality namespace Fabio { -// import 'fabio' Python module +//! Imports 'fabio' Python module PyObjectPtr import(); -// call 'fabio.open' on a given file (needs Numpy) +//! Calls 'fabio.open' on a given file (needs Numpy) PyObjectPtr open(const std::string& filename, PyObjectPtr& fabio_module); } // namespace Fabio diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index 57d3e4a9120..48d56c60683 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -24,11 +24,13 @@ class MultiLayer; namespace PythonInterpreter { -//! Creates a multi layer by running python code in embedded interpreter. -//! @param script: Python script -//! @param functionName: A function name in this script which produces a MultiLayer -//! @param path: A path to import BornAgain library. If empty, relies on PYTHONPATH -//! @param multilayer_ptr: a void pointer to the resulting MultiLayer instance in C++ +//! Creates a multi layer by running python code in embedded interpreter, given +//! The Python-script filename and the name of the function which produces a `MultiLayer`. +//! A path can be given to import `BornAgain` library (if empty, relies on `PYTHONPATH`). +//! Finally, if successful, `multilayer_ptr` will be a void pointer to the resulting +// `MultiLayer` instance in C++ (memory should be managed by the caller). +//! The returned value is the PyObject corresponding the intermediate _Python_ instance of +//! of the `MultiLayer` class. PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& script, const std::string& functionName, -- GitLab From 27ca76f88a60bf99cfe3a1f0f35e6f56db2a65c3 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Mon, 8 May 2023 12:24:21 +0200 Subject: [PATCH 48/85] PyObjectPtr: make the raw pointer to the Python object a private member --- Device/Data/Datafield.cpp | 2 +- PyCore/Embed/PyObjectPtr.cpp | 22 +++++++++++++--------- PyCore/Embed/PyObjectPtr.h | 9 ++++++--- PyCore/Embed/PythonInterpreter.cpp | 6 +++--- PyCore/Sample/ImportMultiLayer.cpp | 2 +- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/Device/Data/Datafield.cpp b/Device/Data/Datafield.cpp index f9f4aa00b3b..e9e6d96fed1 100644 --- a/Device/Data/Datafield.cpp +++ b/Device/Data/Datafield.cpp @@ -231,7 +231,7 @@ PyObject* Datafield::npArray() const return nullptr; // get the pointer to the data buffer of the array (assumed to be C-contiguous) - double* data{PythonInterpreter::Numpy::getDataPtr(pyarray.ptr)}; + double* data{PythonInterpreter::Numpy::getDataPtr(pyarray.get())}; if (!data) return nullptr; diff --git a/PyCore/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp index d951b44ecf7..d4ff222b511 100644 --- a/PyCore/Embed/PyObjectPtr.cpp +++ b/PyCore/Embed/PyObjectPtr.cpp @@ -43,12 +43,12 @@ Status::operator bool() const //---------------------------------------- PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr, const Status& status) - : ptr{pyobject_ptr} - , status{status} + : status{status} + , m_ptr{pyobject_ptr} { // if PyObject pointer is null but result type is not `None` or `Error`, // then that will be an `Error` case. - if (!ptr && status.type != Status::Type::None && status.type != Status::Type::Error) { + if (!m_ptr && status.type != Status::Type::None && status.type != Status::Type::Error) { this->status.type = Status::Type::Error; } } @@ -73,16 +73,20 @@ PyObjectPtr::~PyObjectPtr() PyObjectPtr::PyObjectPtr(PyObjectPtr&& other) - : PyObjectPtr(other.ptr, other.status) + : PyObjectPtr(other.get(), other.status) { // reset the moved object other.reset(); } +PyObject* PyObjectPtr::get() +{ + return m_ptr; +} PyObject* PyObjectPtr::release() { - PyObject* pyobject_ptr{ptr}; + PyObject* pyobject_ptr{m_ptr}; reset(); return pyobject_ptr; } @@ -90,7 +94,7 @@ PyObject* PyObjectPtr::release() void PyObjectPtr::reset() { - ptr = nullptr; + m_ptr = nullptr; status = Status(); } @@ -103,18 +107,18 @@ void PyObjectPtr::discard() "(segmentation fault)")); } - PythonInterpreter::DecRef(ptr); + PythonInterpreter::DecRef(m_ptr); reset(); } bool PyObjectPtr::valid() const { - return (ptr && (status.isOK() || status.isWarning())); + return (m_ptr && (status.isOK() || status.isWarning())); } bool PyObjectPtr::isSet() const { - return (ptr && status.isSet()); + return (m_ptr && status.isSet()); } diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index a8c5a3f1ecc..ae4061f6015 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -51,9 +51,6 @@ public: class PyObjectPtr { public: - //! Raw pointer to the PyObject - PyObject* ptr = nullptr; - //! Status of the PyObject Status status; @@ -65,6 +62,8 @@ public: PyObjectPtr(PyObjectPtr&& other); PyObjectPtr& operator=(const PyObjectPtr&) = delete; + //! Returns the raw pointer to the PyObject + PyObject* get(); //! Resets the container to the initial status (does _not_ release the Python resource) void reset(); //! Resets the container to the initial status and return the PyObject pointer @@ -76,6 +75,10 @@ public: bool valid() const; //! Checks whether the PyObjectPtr is set bool isSet() const; + +private: + //! Raw pointer to the PyObject + PyObject* m_ptr = nullptr; }; diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index 69228a73236..acba3c00d24 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -697,7 +697,7 @@ PythonInterpreter::BornAgain::listOfFunctions(const std::string& script, const s { PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; - PyObject* pDict = PyModule_GetDict(tmpModule.ptr); + PyObject* pDict = PyModule_GetDict(tmpModule.get()); if (!pDict) { checkError(); throw(std::runtime_error("PythonInterpreter::BornAgain: " @@ -732,13 +732,13 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject // load an image via calling `fabio.load` function which takes a // filename (Python string) and returns a Numpy array. - if (!fabio_module.valid() || !PyModule_Check(fabio_module.ptr)) { + if (!fabio_module.valid() || !PyModule_Check(fabio_module.get())) { return {Status(Status::Type::Error, errorDescription("PythonInterpreter.fabio: Invalid Python module " "(expected 'fabio' module)"))}; } - PyObject* pFunc = PyObject_GetAttrString(fabio_module.ptr, (char*)"open"); + PyObject* pFunc = PyObject_GetAttrString(fabio_module.get(), (char*)"open"); if (!pFunc || !PyCallable_Check(pFunc)) { PythonInterpreter::checkError(); return {Status(Status::Type::Error, diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index 3572d83f0da..66d674de1a9 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -18,7 +18,7 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; // locate the `get_simulation` function (it is an attribute of the module) - PyObject* pAddFn = PyObject_GetAttrString(tmpModule.ptr, functionName.c_str()); + PyObject* pAddFn = PyObject_GetAttrString(tmpModule.get(), functionName.c_str()); if (!pAddFn) { std::string msg = "PythonInterpreter::BornAgain: " "Cannot locate the compiled function '" + functionName + "'"; -- GitLab From 8b9e516557359914263d23390becd748e11c6d34 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 9 May 2023 10:24:02 +0200 Subject: [PATCH 49/85] PyCore/Sample/ImportMultiLayer: fix the check for SWIG macro definition --- PyCore/Sample/ImportMultiLayer.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index 48d56c60683..998e952cad3 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -15,7 +15,9 @@ #ifndef BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H #define BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H -#ifndef SWIG +#ifdef SWIG +#error no need to expose this header to Swig +#endif #include "PyCore/Embed/PyObjectPtr.h" #include <string> @@ -38,6 +40,4 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, } // namespace PythonInterpreter -#endif // SWIG - #endif // BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H -- GitLab From cd33d691e15bc7ed00f1f2c5f783441e2eb272f2 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 9 May 2023 10:26:44 +0200 Subject: [PATCH 50/85] PyCore: fix include statements --- PyCore/Embed/PyObjectPtr.h | 4 ++-- PyCore/Embed/PythonInterpreter.cpp | 2 +- PyCore/Embed/PythonInterpreter.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index ae4061f6015..858c80da365 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -15,9 +15,9 @@ #ifndef BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H #define BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H -#include "PyObjectDecl.h" -#include <vector> +#include "PyCore/Embed/PyObjectDecl.h" #include <string> +#include <vector> //! Indicator for the status of a result class Status { diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index acba3c00d24..5002c10fedd 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -14,7 +14,7 @@ References: */ #define INCLUDE_NUMPY // also include Numpy via PyCore -#include "PyCore.h" +#include "PyCore/Embed/PyCore.h" #undef INCLUDE_NUMPY #include "PythonInterpreter.h" diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index e98a1ae63cc..412a4454afb 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -15,7 +15,7 @@ #ifndef BORNAGAIN_PYCORE_EMBED_PYTHONINTERPRETER_H #define BORNAGAIN_PYCORE_EMBED_PYTHONINTERPRETER_H -#include "PyObjectPtr.h" +#include "PyCore/Embed/PyObjectPtr.h" #include <string> #include <vector> -- GitLab From 3ff62454b497707d48fa45a52e02de4a8aca14b6 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Tue, 9 May 2023 10:27:23 +0200 Subject: [PATCH 51/85] PyCore: rm Makefile (used only for debugging) --- PyCore/Embed/Makefile | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 PyCore/Embed/Makefile diff --git a/PyCore/Embed/Makefile b/PyCore/Embed/Makefile deleted file mode 100644 index 57a593ed7eb..00000000000 --- a/PyCore/Embed/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -.PHONY: all clean py - -all: py - - -CC=gcc -LINK=gcc -CXX=g++ - -CXX_INCLUDES=-I/usr/include/python3.9 -I$${HOME}/Projects/bornagain -I$${HOME}/.local/opt/libheinz/include -CXX_LINKS=-lpython3.9 - -BA_FLAGS=-DBORNAGAIN_PYTHON -CXX_CFLAGS=-Wall -Wextra -Wpedantic -fsanitize=undefined ${BA_FLAGS} ${CXX_INCLUDES} -CXX_LFLAGS= ${CXX_LINKS} # -Wl,-verbose -CPP_CFLAGS=-std=c++17 - -py: PythonInterpreter.cpp PythonInterpreter.h PyObjectPtr.cpp PyObjectDecl.h - @echo "Making an embedded Python interpreter..." - ${CXX} -c -fPIC Result.cpp ${CPP_CFLAGS} ${CXX_CFLAGS} - ${CXX} -c -fPIC PythonInterpreter.cpp PyObjectPtr.cpp ${CPP_CFLAGS} ${CXX_CFLAGS} - ${CXX} -shared -o libpythonembed.so PythonInterpreter.o PyObjectPtr.o Result.o ${CXX_CFLAGS} - ${CXX} -o testEmbeddedPython.exe test_embedded_python.cpp ${CXX_CFLAGS} ${CPP_CFLAGS} -L. -lpythonembed ${CXX_LFLAGS} '-Wl,--disable-new-dtags,-rpath,$$ORIGIN' - - -clean: - @echo "Cleaning all products..." - rm *.o *.out *.exe -- GitLab From 988998e13d353138d5a03013d11debf022baf4ad Mon Sep 17 00:00:00 2001 From: "a.nejati.debug@debian-ci" <a.nejati.debug@debian-ci> Date: Mon, 8 May 2023 15:07:09 +0200 Subject: [PATCH 52/85] Apply clang-format --- GUI/View/Project/PyImportAssistant.cpp | 9 +- PyCore/Embed/PyObjectPtr.h | 3 +- PyCore/Embed/PythonInterpreter.cpp | 124 +++++++++++++------------ PyCore/Embed/PythonInterpreter.h | 6 +- PyCore/Sample/ImportMultiLayer.cpp | 17 ++-- PyCore/Sample/ImportMultiLayer.h | 3 +- 6 files changed, 82 insertions(+), 80 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index a1308c8c465..2ff81c4b76a 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -29,7 +29,7 @@ #include "GUI/View/Info/MessageBox.h" #include "GUI/View/Project/ProjectManager.h" #include "GUI/View/Tool/Globals.h" -#include "PyCore/Embed/PythonInterpreter.h" // listOfFunctions +#include "PyCore/Embed/PythonInterpreter.h" // listOfFunctions #include "PyCore/Sample/ImportMultiLayer.h" // createMultiLayerFromPython #include "Sample/Multilayer/MultiLayer.h" #include <QApplication> @@ -104,11 +104,10 @@ std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QStri { QApplication::setOverrideCursor(Qt::WaitCursor); void* result_ptr = nullptr; - PyObjectPtr result{PythonInterpreter::createMultiLayerFromPython(result_ptr, - snippet.toStdString(), funcName.toStdString(), bornagainDir())}; + PyObjectPtr result{PythonInterpreter::createMultiLayerFromPython( + result_ptr, snippet.toStdString(), funcName.toStdString(), bornagainDir())}; - std::unique_ptr<MultiLayer> multilayer_ptr( - reinterpret_cast<MultiLayer*>(result_ptr)->clone()); + std::unique_ptr<MultiLayer> multilayer_ptr(reinterpret_cast<MultiLayer*>(result_ptr)->clone()); if (!result.valid()) { QApplication::restoreOverrideCursor(); diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 858c80da365..dca6fb3cf49 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -83,8 +83,7 @@ private: //! Numpy array descriptors -struct NumpyArrayDescriptor -{ +struct NumpyArrayDescriptor { //! C-contiguous array flag bool C_contiguous = false; //! FORTRAN-contiguous array flag diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index 5002c10fedd..f03d99cf2bd 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -19,13 +19,13 @@ References: #include "PythonInterpreter.h" -#include <cstddef> // NULL -#include <cstring> // memcpy -#include <memory> // unique_ptr +#include <cstddef> // NULL +#include <cstring> // memcpy +#include <iostream> // cerr +#include <memory> // unique_ptr +#include <stdexcept> // runtime_error #include <string> #include <vector> -#include <stdexcept> // runtime_error -#include <iostream> // cerr namespace { @@ -83,10 +83,9 @@ std::vector<std::string> toVectorString(PyObject* py_object) result.push_back(PythonInterpreter::pyStrtoString(value)); } } else { - throw(std::runtime_error( - PythonInterpreter::errorDescription( - "PythonInterpreter: Cannnot convert the given Python object " - "to vector<string>."))); + throw(std::runtime_error(PythonInterpreter::errorDescription( + "PythonInterpreter: Cannnot convert the given Python object " + "to vector<string>."))); } return result; @@ -124,14 +123,12 @@ Status PythonInterpreter::initialize() if (!Py_IsInitialized()) { Py_Initialize(); return {Status::Type::Info, - "PythonInterpreter: Initialized Python - version: " - + toString(Py_GetVersion())}; + "PythonInterpreter: Initialized Python - version: " + toString(Py_GetVersion())}; } // if Python already initialized - return {Status::Type::Warning, - "PythonInterpreter: Python already initialized - version: " - + toString(Py_GetVersion())}; + return {Status::Type::Warning, "PythonInterpreter: Python already initialized - version: " + + toString(Py_GetVersion())}; } // Python stable ABI @@ -149,7 +146,7 @@ Status PythonInterpreter::finalize() Py_Finalize(); return {Status::Type::Info, - "PythonInterpreter: Finalized Python - version: " + toString(Py_GetVersion())}; + "PythonInterpreter: Finalized Python - version: " + toString(Py_GetVersion())}; } // Python stable ABI @@ -227,10 +224,10 @@ std::string PythonInterpreter::runtimeInfo() // Runtime environment result = std::string(60, '=') + "\n"; // Embedded Python details - result =+ "Py_GetProgramName(): " + toString(Py_GetProgramName()) + "\n"; - result =+ "Py_GetProgramFullPath(): " + toString(Py_GetProgramFullPath()) + "\n"; - result =+ "Py_GetPath(): " + toString(Py_GetPath()) + "\n"; - result =+ "Py_GetPythonHome(): " + toString(Py_GetPythonHome()) + "\n"; + result = +"Py_GetProgramName(): " + toString(Py_GetProgramName()) + "\n"; + result = +"Py_GetProgramFullPath(): " + toString(Py_GetProgramFullPath()) + "\n"; + result = +"Py_GetPath(): " + toString(Py_GetPath()) + "\n"; + result = +"Py_GetPythonHome(): " + toString(Py_GetPythonHome()) + "\n"; // Runtime Python's sys.path PyObject* sysPath = PySys_GetObject((char*)"path"); @@ -330,8 +327,8 @@ bool PythonInterpreter::Numpy::isInitialized() NumpyArrayDescriptor PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobject_ptr) { if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { - throw(std::runtime_error( - errorDescription("PythonInterpreter::Numpy: Invalid Numpy array"))); + throw( + std::runtime_error(errorDescription("PythonInterpreter::Numpy: Invalid Numpy array"))); } PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; @@ -369,13 +366,12 @@ NumpyArrayDescriptor PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobjec return np_descr_out; } -std::vector<double> -PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) +std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) { if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { throw(std::runtime_error( - errorDescription("PythonInterpreter::Numpy: Cannot convert an invalid " - "Numpy array to a C-Array"))); + errorDescription("PythonInterpreter::Numpy: Cannot convert an invalid " + "Numpy array to a C-Array"))); } PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; @@ -391,18 +387,20 @@ PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) // Numpy array type must be 2d if (npArray_ndim != 2) { std::string msg = "PythonInterpreter::Numpy: Expected a Numpy 2d-array " - "(given number of dimensions is " + std::to_string(npArray_ndim) + ")"; + "(given number of dimensions is " + + std::to_string(npArray_ndim) + ")"; throw(std::runtime_error( - errorDescription("PythonInterpreter::Numpy: Expected a Numpy 2d-array " - "(given number of dimensions is " - + std::to_string(npArray_ndim) + ")"))); + errorDescription("PythonInterpreter::Numpy: Expected a Numpy 2d-array " + "(given number of dimensions is " + + std::to_string(npArray_ndim) + ")"))); } // Numpy array type must be numeric and real (eligible for casting to double) if (!PyDataType_ISNUMBER(npArray_descr) || PyDataType_ISCOMPLEX(npArray_descr)) { std::string msg = "PythonInterpreter::Numpy: " - "Expected a Numpy array of numeric type and real " - "(given type '" + std::to_string(npArray_dtype) + "')"; + "Expected a Numpy array of numeric type and real " + "(given type '" + + std::to_string(npArray_dtype) + "')"; throw(std::runtime_error(errorDescription(msg))); } @@ -459,11 +457,11 @@ PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) #endif // _WIN32 default: - throw(std::runtime_error( - errorDescription("PythonInterpreter::Numpy: " - "Conversion of Numpy array of dtype '" - + std::to_string(npArray_dtype) + "' " - "to vector<double> is not implemented"))); + throw(std::runtime_error(errorDescription("PythonInterpreter::Numpy: " + "Conversion of Numpy array of dtype '" + + std::to_string(npArray_dtype) + + "' " + "to vector<double> is not implemented"))); } return data; @@ -492,8 +490,9 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, if (!npArray_ptr) { checkError(); std::string msg = "PythonInterpreter::Numpy: " - "Cannot create a Numpy 1D-array from the " - "given data (size = " + std::to_string(size) + ")"; + "Cannot create a Numpy 1D-array from the " + "given data (size = " + + std::to_string(size) + ")"; return {Status(Status::Type::Error, errorDescription(msg))}; } @@ -519,9 +518,10 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, } if (size < 1) { - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter::Numpy: " - "Cannot create a Numpy 1D-array from a data with size = 0"))}; + return { + Status(Status::Type::Error, + errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a data with size = 0"))}; } npy_intp npDims[1] = {size}; @@ -531,8 +531,9 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, if (!npArray_ptr) { checkError(); std::string msg = "PythonInterpreter::Numpy: " - "Cannot create a Numpy 1D-array from the " - "given data (size = " + std::to_string(size) + ")"; + "Cannot create a Numpy 1D-array from the " + "given data (size = " + + std::to_string(size) + ")"; return {Status(Status::Type::Error, errorDescription(msg))}; } @@ -552,16 +553,18 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const np_size_t dims[2]) { if (!c_array) { - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter::Numpy: " - "Cannot create a Numpy 2D-array from a null data pointer"))}; + return { + Status(Status::Type::Error, + errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 2D-array from a null data pointer"))}; } const np_size_t size = dims[0] * dims[1]; if (size < 1) { - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter::Numpy: " - "Cannot create a Numpy 2D-array from a data with size = 0"))}; + return { + Status(Status::Type::Error, + errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 2D-array from a data with size = 0"))}; } npy_intp npDims[2] = {dims[0], dims[1]}; @@ -611,7 +614,7 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio if (!npyArray_ptr) { checkError(); std::string msg = "PythonInterpreter::Numpy: Cannot create a Numpy " - + std::to_string(n_dims) + "D-array from the given data"; + + std::to_string(n_dims) + "D-array from the given data"; return {Status(Status::Type::Error, errorDescription(msg))}; } @@ -628,9 +631,8 @@ double* PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) if (!data_ptr) { checkError(); - throw(std::runtime_error( - errorDescription("PythonInterpreter::Numpy: " - "Numpy array has invalid data pointer"))); + throw(std::runtime_error(errorDescription("PythonInterpreter::Numpy: " + "Numpy array has invalid data pointer"))); } return data_ptr; @@ -654,7 +656,8 @@ PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) if (!ba_pymodule || !PyModule_Check(ba_pymodule)) { checkError(); std::string msg = "PythonInterpreter: Cannot load 'bornagain' Python module " - "(given path = '" + path + "')"; + "(given path = '" + + path + "')"; return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg))}; } @@ -692,8 +695,8 @@ PyObjectPtr PythonInterpreter::BornAgain::importScript(const std::string& script } -std::vector<std::string> -PythonInterpreter::BornAgain::listOfFunctions(const std::string& script, const std::string& path) +std::vector<std::string> PythonInterpreter::BornAgain::listOfFunctions(const std::string& script, + const std::string& path) { PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; @@ -751,9 +754,9 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject if (!pFilename) { PythonInterpreter::checkError(); return {Status(Status::Type::Error, - errorDescription("PythonInterpreter.fabio: Filename '" - + filename + "' " - "cannot be converted to Python unicode string"))}; + errorDescription("PythonInterpreter.fabio: Filename '" + filename + + "' " + "cannot be converted to Python unicode string"))}; } // get the result of the call `fabio.open(<filename>)` @@ -764,7 +767,8 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject return {Status(Status::Type::Error, errorDescription("PythonInterpreter.fabio: " "Invalid return value from calling the function " - "'fabio.open(\"" + filename + "\")'"))}; + "'fabio.open(\"" + + filename + "\")'"))}; } // get `result.data` (must be a Numpy array) diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index 412a4454afb..2702649333a 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -3,7 +3,8 @@ // BornAgain: simulate and fit reflection and scattering // //! @file PyCore/Embed/PythonInterpreter.h -//! @brief Definition of functions and classes to expose Python-interpreter functionality to C++. +//! @brief Definition of functions and classes to expose Python-interpreter functionality to +//! C++. //! //! @homepage http://www.bornagainproject.org //! @license GNU General Public License v3 or higher (see COPYING) @@ -103,8 +104,7 @@ PyObjectPtr importScript(const std::string& script, const std::string& path); //! Returns list of functions defined in the script, given a script filename and //! a path to import BornAgain library (if empty, relies on `PYTHONPATH`). -std::vector<std::string> listOfFunctions(const std::string& script, - const std::string& path); +std::vector<std::string> listOfFunctions(const std::string& script, const std::string& path); } // namespace BornAgain diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index 66d674de1a9..45550aa80ef 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -1,7 +1,7 @@ #ifdef BORNAGAIN_PYTHON -#include "PyCore/Embed/PyCore.h" #include "ImportMultiLayer.h" +#include "PyCore/Embed/PyCore.h" #include "PyCore/Embed/PythonInterpreter.h" // BornAgain::importScript // SWIG runtime access for creating a MultiLayer instance from Python @@ -10,10 +10,8 @@ namespace PythonInterpreter { -PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, - const std::string& script, - const std::string& functionName, - const std::string& path) +PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& script, + const std::string& functionName, const std::string& path) { PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; @@ -21,7 +19,8 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, PyObject* pAddFn = PyObject_GetAttrString(tmpModule.get(), functionName.c_str()); if (!pAddFn) { std::string msg = "PythonInterpreter::BornAgain: " - "Cannot locate the compiled function '" + functionName + "'"; + "Cannot locate the compiled function '" + + functionName + "'"; return {Status(Status::Type::Error, msg)}; } @@ -33,7 +32,8 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, if (!instance) { std::string msg = "PythonInterpreter::BornAgain: " - "Cannot call the function '" + functionName + "'"; + "Cannot call the function '" + + functionName + "'"; return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg))}; } @@ -49,7 +49,8 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, if (!SWIG_IsOK(res)) { Py_DecRef(instance); std::string msg = "PythonInterpreter::BornAgain: SWIG failed to extract a " - "'MultiLayer' instance via calling the function '" + functionName + "'"; + "'MultiLayer' instance via calling the function '" + + functionName + "'"; return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg))}; } diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index 998e952cad3..97aa9127fd7 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -33,8 +33,7 @@ namespace PythonInterpreter { // `MultiLayer` instance in C++ (memory should be managed by the caller). //! The returned value is the PyObject corresponding the intermediate _Python_ instance of //! of the `MultiLayer` class. -PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, - const std::string& script, +PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& script, const std::string& functionName, const std::string& path = ""); -- GitLab From 6bea4228514db4e3a4d95bba36e6afa12e4703dd Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Wed, 10 May 2023 11:34:27 +0200 Subject: [PATCH 53/85] PyCore: rm Status class --- Device/Data/Datafield.cpp | 4 +- GUI/View/Project/PyImportAssistant.cpp | 3 +- PyCore/Embed/PyObjectPtr.cpp | 75 +--------- PyCore/Embed/PyObjectPtr.h | 39 +---- PyCore/Embed/PythonInterpreter.cpp | 198 ++++++++++++------------- PyCore/Embed/PythonInterpreter.h | 12 +- PyCore/Sample/ImportMultiLayer.cpp | 25 ++-- 7 files changed, 119 insertions(+), 237 deletions(-) diff --git a/Device/Data/Datafield.cpp b/Device/Data/Datafield.cpp index e9e6d96fed1..d79a326dbf1 100644 --- a/Device/Data/Datafield.cpp +++ b/Device/Data/Datafield.cpp @@ -211,9 +211,7 @@ PyObject* Datafield::npArray() const { // TODO: Thoroughly check this function regarding index manipulations - Status init{PythonInterpreter::Numpy::initialize()}; - if (init.isError()) - return nullptr; + PythonInterpreter::Numpy::initialize(); ASSERT(rank() <= 2); diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index 2ff81c4b76a..169d4cdde7f 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -112,8 +112,7 @@ std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QStri if (!result.valid()) { QApplication::restoreOverrideCursor(); QString message("Exception thrown while executing Python code to create sample.\n\n"); - QString details = QString::fromStdString(result.status.message); - DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, details).exec(); + DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, "").exec(); } QApplication::restoreOverrideCursor(); diff --git a/PyCore/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp index d4ff222b511..6f91d8b395d 100644 --- a/PyCore/Embed/PyObjectPtr.cpp +++ b/PyCore/Embed/PyObjectPtr.cpp @@ -2,78 +2,19 @@ #include "PythonInterpreter.h" #include <stdexcept> // runtime_error -#include <string> - - -Status::Status(const Status::Type type, const std::string& message) - : type{type} - , message{message} -{ -} - -bool Status::isSet() const -{ - return (type != Status::Type::None); -} - - -bool Status::isOK() const -{ - return (type == Status::Type::Info); -} - - -bool Status::isWarning() const -{ - return (type == Status::Type::Warning); -} - - -bool Status::isError() const -{ - return (type == Status::Type::Error); -} - - -Status::operator bool() const -{ - return isOK() || isWarning(); -} - -//---------------------------------------- - -PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr, const Status& status) - : status{status} - , m_ptr{pyobject_ptr} -{ - // if PyObject pointer is null but result type is not `None` or `Error`, - // then that will be an `Error` case. - if (!m_ptr && status.type != Status::Type::None && status.type != Status::Type::Error) { - this->status.type = Status::Type::Error; - } -} - PyObjectPtr::PyObjectPtr(PyObject* pyobject_ptr) - : PyObjectPtr(pyobject_ptr, Status(Status::Type::Info)) + : m_ptr{pyobject_ptr} { } - -PyObjectPtr::PyObjectPtr(const Status& status) - : PyObjectPtr(nullptr, status) -{ -} - - PyObjectPtr::~PyObjectPtr() { discard(); } - PyObjectPtr::PyObjectPtr(PyObjectPtr&& other) - : PyObjectPtr(other.get(), other.status) + : PyObjectPtr(other.get()) { // reset the moved object other.reset(); @@ -91,14 +32,11 @@ PyObject* PyObjectPtr::release() return pyobject_ptr; } - void PyObjectPtr::reset() { m_ptr = nullptr; - status = Status(); } - void PyObjectPtr::discard() { if (!PythonInterpreter::isInitialized()) { @@ -111,14 +49,7 @@ void PyObjectPtr::discard() reset(); } - bool PyObjectPtr::valid() const { - return (m_ptr && (status.isOK() || status.isWarning())); -} - - -bool PyObjectPtr::isSet() const -{ - return (m_ptr && status.isSet()); + return static_cast<bool>(m_ptr); } diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index dca6fb3cf49..b397a8f8066 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -16,47 +16,16 @@ #define BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H #include "PyCore/Embed/PyObjectDecl.h" -#include <string> #include <vector> -//! Indicator for the status of a result -class Status { -public: - enum class Type { None = -1, Info, Warning, Error }; - Type type = Type::None; - - //! Status message - std::string message; - - Status(const Status::Type type = Type::None, const std::string& message = ""); - - bool isSet() const; - - bool isOK() const; - - bool isWarning() const; - - bool isError() const; - - //! Cast the validity status to boolean (true if result is valid) - operator bool() const; -}; - - -//! Safe container for PyObjects -//! The class `PyObjectPtr` contains a `PyObject*` (or `NULL`) and -//! a `Status` which denotes the status of the value (OK/Warning/Error). +//! Safe container for PyObjects: +//! The class `PyObjectPtr` contains a `PyObject*` (or `NULL`). //! Decrementing Python reference-count is performed automatically when -//! a `PyObjectPtr` expires. +//! the life-time of a `PyObjectPtr` expires. class PyObjectPtr { public: - //! Status of the PyObject - Status status; - - PyObjectPtr(PyObject* pyobject_ptr, const Status& status); PyObjectPtr(PyObject* pyobject_ptr); - PyObjectPtr(const Status& status); ~PyObjectPtr(); PyObjectPtr(const PyObjectPtr&) = delete; PyObjectPtr(PyObjectPtr&& other); @@ -73,8 +42,6 @@ public: void discard(); //! Check the validity of the PyObjectPtr bool valid() const; - //! Checks whether the PyObjectPtr is set - bool isSet() const; private: //! Raw pointer to the PyObject diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index f03d99cf2bd..95d1c0a6259 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -27,6 +27,7 @@ References: #include <string> #include <vector> + namespace { // thin wrapper to initialize Numpy @@ -42,9 +43,9 @@ std::nullptr_t _init_numpy(int& status) */ // NOTE: Numpy's `import_array` returns `NULL` on failure; // hence return type must be `std::nullptr_t`. - status = 0; + status = 1; import_array(); - status = 1; // Numpy Array API loaded successfully + status = 0; // Numpy Array API loaded successfully return NULL; } @@ -83,9 +84,9 @@ std::vector<std::string> toVectorString(PyObject* py_object) result.push_back(PythonInterpreter::pyStrtoString(value)); } } else { - throw(std::runtime_error(PythonInterpreter::errorDescription( + throw std::runtime_error(PythonInterpreter::errorDescription( "PythonInterpreter: Cannnot convert the given Python object " - "to vector<string>."))); + "to vector<string>.")); } return result; @@ -116,19 +117,13 @@ inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, npy_intp rowI // see <https://docs.python.org/3/c-api/stable.html> // Python stable ABI -Status PythonInterpreter::initialize() +void PythonInterpreter::initialize() { - std::string msg; - if (!Py_IsInitialized()) { Py_Initialize(); - return {Status::Type::Info, - "PythonInterpreter: Initialized Python - version: " + toString(Py_GetVersion())}; } - // if Python already initialized - return {Status::Type::Warning, "PythonInterpreter: Python already initialized - version: " - + toString(Py_GetVersion())}; + // if Python interpreter is already initialized, do nothing } // Python stable ABI @@ -138,29 +133,26 @@ bool PythonInterpreter::isInitialized() } // Python stable ABI -Status PythonInterpreter::finalize() +void PythonInterpreter::finalize() { // undo all initializations made by Py_Initialize() and subsequent use // of Python/C API functions, and destroy all sub-interpreters. // This is a no-op when called for a second time. Py_Finalize(); - - return {Status::Type::Info, - "PythonInterpreter: Finalized Python - version: " + toString(Py_GetVersion())}; } // Python stable ABI -Status PythonInterpreter::checkError() +bool PythonInterpreter::checkError() { if (PyErr_Occurred()) { // print a standard traceback to sys.stderr and clear the error indicator std::cerr << "---PythonInterpreter: Error in Python interpreter:\n"; PyErr_Print(); std::cerr << "\n---\n"; - return {Status::Type::Error}; + return true; } - return {Status::Type::Info, "No Python errors occured"}; + return false; } // Python stable ABI @@ -171,14 +163,20 @@ void PythonInterpreter::addPythonPath(const std::string& path) PyObject* sysPath = PySys_GetObject((char*)"path"); PyList_Append(sysPath, PyUnicode_FromString(path.c_str())); } + + throw std::runtime_error("PythonInterpreter.addPythonPath: Cannot add to an empty Python path."); } // Python stable ABI -int PythonInterpreter::setPythonPath(const std::string& path) +void PythonInterpreter::setPythonPath(const std::string& path) { // returns 0 on success, -1 on error const int result = PySys_SetObject((char*)"path", PyUnicode_FromString(path.c_str())); - return result; + if (result != 0) { + PythonInterpreter::checkError(); + throw std::runtime_error("PythonInterpreter.setPythonPath: Cannot set the Python path."); + } + } // Python stable ABI @@ -190,9 +188,9 @@ PyObjectPtr PythonInterpreter::import(const std::string& pymodule_name, const st PyObject* pymodule = PyImport_ImportModule(pymodule_name.c_str()); if (!pymodule || !PyModule_Check(pymodule)) { checkError(); - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter: Cannot load Python module '" - + pymodule_name + "' (given path = '" + path + "')"))}; + throw std::runtime_error( + errorDescription("PythonInterpreter: Cannot load Python module '" + + pymodule_name + "' (given path = '" + path + "')")); } // returns a _new_ reference; ie. caller is responsible for the ref-count @@ -296,25 +294,25 @@ std::string PythonInterpreter::errorDescription(const std::string& title) //---------------------------------------- -Status PythonInterpreter::Numpy::initialize() +int PythonInterpreter::Numpy::initialize() { // initialize Python C API, if needed - Status py_status{PythonInterpreter::initialize()}; - if (py_status.isError()) - return py_status; + PythonInterpreter::initialize(); int res; _init_numpy(res); switch (res) { + case 0: + return res; case 1: - return {Status::Type::Info, "PythonInterpreter: Initialized Numpy"}; + throw std::runtime_error(errorDescription("PythonInterpreter: Cannot initialize Numpy")); case 2: - return {Status::Type::Warning, "PythonInterpreter: Numpy already initialized"}; - default: - return {Status::Type::Error, "PythonInterpreter: Cannot initialize Numpy"}; + return res; } + + return res; } bool PythonInterpreter::Numpy::isInitialized() @@ -389,10 +387,10 @@ std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* std::string msg = "PythonInterpreter::Numpy: Expected a Numpy 2d-array " "(given number of dimensions is " + std::to_string(npArray_ndim) + ")"; - throw(std::runtime_error( + throw std::runtime_error( errorDescription("PythonInterpreter::Numpy: Expected a Numpy 2d-array " "(given number of dimensions is " - + std::to_string(npArray_ndim) + ")"))); + + std::to_string(npArray_ndim) + ")")); } // Numpy array type must be numeric and real (eligible for casting to double) @@ -401,7 +399,7 @@ std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* "Expected a Numpy array of numeric type and real " "(given type '" + std::to_string(npArray_dtype) + "')"; - throw(std::runtime_error(errorDescription(msg))); + throw std::runtime_error(errorDescription(msg)); } std::vector<double> data(npArray_size); @@ -457,11 +455,11 @@ std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* #endif // _WIN32 default: - throw(std::runtime_error(errorDescription("PythonInterpreter::Numpy: " + throw std::runtime_error(errorDescription("PythonInterpreter::Numpy: " "Conversion of Numpy array of dtype '" + std::to_string(npArray_dtype) + "' " - "to vector<double> is not implemented"))); + "to vector<double> is not implemented")); } return data; @@ -471,16 +469,14 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, const np_size_t dims[2]) { if (!c_array) { - return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: " - "Cannot create a Numpy 1D-array from a null data pointer")}; + throw std::runtime_error("PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a null data pointer"); } const np_size_t size = dims[0] * dims[1]; if (size < 1) { - return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: " - "Cannot create a Numpy 1D-array from a data with size = 0")}; + throw std::runtime_error("PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a data with size = 0"); } npy_intp npDims[2] = {dims[0], dims[1]}; @@ -489,11 +485,10 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 2, npDims, NPY_DOUBLE); if (!npArray_ptr) { checkError(); - std::string msg = "PythonInterpreter::Numpy: " - "Cannot create a Numpy 1D-array from the " - "given data (size = " - + std::to_string(size) + ")"; - return {Status(Status::Type::Error, errorDescription(msg))}; + throw std::runtime_error("PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from the " + "given data (size = " + + std::to_string(size) + ")"); } // obtain pointer to data buffer of Numpy array @@ -512,16 +507,14 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, const np_size_t size) { if (!c_array) { - return {Status(Status::Type::Error, - "PythonInterpreter::Numpy: " - "Cannot create a Numpy 1D-array from a null data pointer")}; + throw std::runtime_error("PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a null data pointer"); } if (size < 1) { - return { - Status(Status::Type::Error, - errorDescription("PythonInterpreter::Numpy: " - "Cannot create a Numpy 1D-array from a data with size = 0"))}; + throw std::runtime_error( + errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from a data with size = 0")); } npy_intp npDims[1] = {size}; @@ -534,7 +527,10 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, "Cannot create a Numpy 1D-array from the " "given data (size = " + std::to_string(size) + ")"; - return {Status(Status::Type::Error, errorDescription(msg))}; + throw std::runtime_error(errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 1D-array from the " + "given data (size = " + + std::to_string(size) + ")")); } // obtain pointer to data buffer of Numpy array @@ -553,18 +549,16 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const np_size_t dims[2]) { if (!c_array) { - return { - Status(Status::Type::Error, - errorDescription("PythonInterpreter::Numpy: " - "Cannot create a Numpy 2D-array from a null data pointer"))}; + throw std::runtime_error( + errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 2D-array from a null data pointer")); } const np_size_t size = dims[0] * dims[1]; if (size < 1) { - return { - Status(Status::Type::Error, - errorDescription("PythonInterpreter::Numpy: " - "Cannot create a Numpy 2D-array from a data with size = 0"))}; + throw std::runtime_error( + errorDescription("PythonInterpreter::Numpy: " + "Cannot create a Numpy 2D-array from a data with size = 0")); } npy_intp npDims[2] = {dims[0], dims[1]}; @@ -575,9 +569,9 @@ PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const if (!npArray_ptr || !PyArray_Check(npArray_ptr)) { PythonInterpreter::checkError(); - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter::Numpy: Cannot convert the given " - "C-Array to a Numpy 2D-array"))}; + throw std::runtime_error( + errorDescription("PythonInterpreter::Numpy: Cannot convert the given " + "C-Array to a Numpy 2D-array")); } // returns a _new_ reference; ie. caller is responsible for the ref-count @@ -589,16 +583,16 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio // descriptors for the array dimensions const std::size_t n_dims = dimensions.size(); if (n_dims < 1) { - return {Status(Status::Type::Error, - errorDescription("Cannot make a Numpy with the given number " - "of dimensions; number of dimensions must be >= 1"))}; + throw std::runtime_error( + errorDescription("Cannot make a Numpy with the given number " + "of dimensions; number of dimensions must be >= 1")); } for (std::size_t d = 0; d < n_dims; ++d) { if (dimensions[d] < 2) { - return {Status(Status::Type::Error, - errorDescription("Cannot make a Numpy with the given dimensions; " - "dimensions must be >= 2"))}; + throw std::runtime_error( + errorDescription("Cannot make a Numpy with the given dimensions; " + "dimensions must be >= 2")); } } @@ -613,9 +607,9 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio if (!npyArray_ptr) { checkError(); - std::string msg = "PythonInterpreter::Numpy: Cannot create a Numpy " - + std::to_string(n_dims) + "D-array from the given data"; - return {Status(Status::Type::Error, errorDescription(msg))}; + throw std::runtime_error( + errorDescription("PythonInterpreter::Numpy: Cannot create a Numpy " + + std::to_string(n_dims) + "D-array from the given data")); } return {npyArray_ptr}; @@ -655,10 +649,9 @@ PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) if (!ba_pymodule || !PyModule_Check(ba_pymodule)) { checkError(); - std::string msg = "PythonInterpreter: Cannot load 'bornagain' Python module " - "(given path = '" - + path + "')"; - return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg))}; + throw std::runtime_error( + errorDescription("PythonInterpreter: Cannot load 'bornagain' Python module " + "(given path = '" + path + "')")); } // restores single handler to make ctr-c alive. @@ -678,8 +671,7 @@ PyObjectPtr PythonInterpreter::BornAgain::importScript(const std::string& script PyObject* pCompiledFn = Py_CompileString(script.c_str(), "", Py_file_input); if (!pCompiledFn) { ba_pymodule.discard(); - return {Status(Status::Type::Error, - PythonInterpreter::errorDescription("Cannot compile Python snippet"))}; + throw std::runtime_error(errorDescription("Cannot compile Python snippet")); } // create a module @@ -687,8 +679,7 @@ PyObjectPtr PythonInterpreter::BornAgain::importScript(const std::string& script if (!tmpModule) { Py_DecRef(pCompiledFn); ba_pymodule.discard(); - return {Status(Status::Type::Error, - PythonInterpreter::errorDescription("Cannot execute Python snippet"))}; + throw std::runtime_error(errorDescription("Cannot execute Python snippet")); } return {tmpModule}; @@ -703,8 +694,8 @@ std::vector<std::string> PythonInterpreter::BornAgain::listOfFunctions(const std PyObject* pDict = PyModule_GetDict(tmpModule.get()); if (!pDict) { checkError(); - throw(std::runtime_error("PythonInterpreter::BornAgain: " - "Cannot obtain the dictionary from the script module")); + throw std::runtime_error("PythonInterpreter::BornAgain: " + "Cannot obtain the dictionary from the script module"); } PyObject *key, *value; @@ -736,27 +727,25 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject // filename (Python string) and returns a Numpy array. if (!fabio_module.valid() || !PyModule_Check(fabio_module.get())) { - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter.fabio: Invalid Python module " - "(expected 'fabio' module)"))}; + throw std::runtime_error( + errorDescription("PythonInterpreter.fabio: Invalid Python module " + "(expected 'fabio' module)")); } PyObject* pFunc = PyObject_GetAttrString(fabio_module.get(), (char*)"open"); if (!pFunc || !PyCallable_Check(pFunc)) { PythonInterpreter::checkError(); - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter.fabio: " - "The function 'fabio.open' is not callable"))}; + throw std::runtime_error( + errorDescription("PythonInterpreter.fabio: The function 'fabio.open' is not callable")); } // convert the filename to a Python unicode string PyObject* pFilename = PyUnicode_FromString(filename.c_str()); if (!pFilename) { PythonInterpreter::checkError(); - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter.fabio: Filename '" + filename - + "' " - "cannot be converted to Python unicode string"))}; + throw std::runtime_error( + errorDescription("PythonInterpreter.fabio: Filename '" + filename + + "' cannot be converted to Python unicode string")); } // get the result of the call `fabio.open(<filename>)` @@ -764,11 +753,10 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject Py_DecRef(pFunc); if (!pResult_open) { PythonInterpreter::checkError(); - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter.fabio: " - "Invalid return value from calling the function " - "'fabio.open(\"" - + filename + "\")'"))}; + std::runtime_error( + errorDescription("PythonInterpreter.fabio: " + "Invalid return value from calling the function " + "'fabio.open(\"" + filename + "\")'")); } // get `result.data` (must be a Numpy array) @@ -776,10 +764,10 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject Py_DecRef(pResult_open); if (!npyArray_ptr || !PyArray_Check(npyArray_ptr)) { PythonInterpreter::checkError(); - return {Status(Status::Type::Error, - errorDescription("PythonInterpreter.fabio: Invalid return value from " - "calling the function 'fabio.open(\"" - + filename + "\")' (expected a Numpy array)"))}; + std::runtime_error( + errorDescription("PythonInterpreter.fabio: Invalid return value from " + "calling the function 'fabio.open(\"" + + filename + "\")' (expected a Numpy array)")); } // returns a _new_ reference; ie. caller is responsible for the ref-count diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index 2702649333a..38df1344f06 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -20,27 +20,25 @@ #include <string> #include <vector> -class MultiLayer; - namespace PythonInterpreter { // Python stable ABI -Status initialize(); +void initialize(); // Python stable ABI bool isInitialized(); // Python stable ABI -Status finalize(); +void finalize(); // Python stable ABI -Status checkError(); +bool checkError(); // Python stable ABI void addPythonPath(const std::string& path); // Python stable ABI -int setPythonPath(const std::string& path); +void setPythonPath(const std::string& path); // Python stable ABI PyObjectPtr import(const std::string& pymodule_name, const std::string& path = ""); @@ -63,7 +61,7 @@ std::string errorDescription(const std::string& title = ""); namespace Numpy { //! Initializes Numpy -Status initialize(); +int initialize(); //! Checks if Numpy is initialized bool isInitialized(); diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index 45550aa80ef..7e015ff08af 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -7,6 +7,8 @@ // SWIG runtime access for creating a MultiLayer instance from Python #include "auto/Wrap/swig_runtime.h" +#include <stdexcept> // runtime_error + namespace PythonInterpreter { @@ -18,10 +20,10 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& // locate the `get_simulation` function (it is an attribute of the module) PyObject* pAddFn = PyObject_GetAttrString(tmpModule.get(), functionName.c_str()); if (!pAddFn) { - std::string msg = "PythonInterpreter::BornAgain: " - "Cannot locate the compiled function '" - + functionName + "'"; - return {Status(Status::Type::Error, msg)}; + throw std::runtime_error( + errorDescription("PythonInterpreter::BornAgain: " + "Cannot locate the compiled function '" + + functionName + "'")); } // create a `MultiLayer` Python object via calling the function @@ -31,10 +33,9 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& Py_DecRef(pAddFn); if (!instance) { - std::string msg = "PythonInterpreter::BornAgain: " - "Cannot call the function '" - + functionName + "'"; - return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg))}; + throw std::runtime_error( + errorDescription("PythonInterpreter::BornAgain: Cannot call the function '" + + functionName + "'")); } // Construct a C++ object from the Python object @@ -48,10 +49,10 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& const int res = SWIG_ConvertPtr(instance, &argp1, pTypeInfo, 0); if (!SWIG_IsOK(res)) { Py_DecRef(instance); - std::string msg = "PythonInterpreter::BornAgain: SWIG failed to extract a " - "'MultiLayer' instance via calling the function '" - + functionName + "'"; - return {Status(Status::Type::Error, PythonInterpreter::errorDescription(msg))}; + throw std::runtime_error( + errorDescription("PythonInterpreter::BornAgain: SWIG failed to extract a " + "'MultiLayer' instance via calling the function '" + + functionName + "'")); } multilayer_ptr = reinterpret_cast<void*>(argp1); -- GitLab From 4f463aab8aab6366694b7ad9a019e2cc7b2182e9 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Wed, 10 May 2023 16:04:22 +0200 Subject: [PATCH 54/85] PythonInterpreter: rm unneeded auxiliary function --- PyCore/Embed/PythonInterpreter.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index 95d1c0a6259..85458f7aee2 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -61,13 +61,6 @@ std::string toString(const wchar_t* const c) return std::string(wstr.begin(), wstr.end()); } -std::string toString(const char* const c) -{ - if (!c) - return ""; - return std::string(c); -} - //! Converts PyObject into vector of strings, if possible, or throws exception std::vector<std::string> toVectorString(PyObject* py_object) { -- GitLab From 1c44f1d34082c6ad0a7ce30b89e29664aa43ed28 Mon Sep 17 00:00:00 2001 From: AlQuemist <alquemist@Lyriks> Date: Thu, 3 Feb 2022 12:06:57 +0100 Subject: [PATCH 55/85] Wrap/Swig/commons.i: Minor improvement to Numpy usage --- Wrap/Swig/commons.i | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Wrap/Swig/commons.i b/Wrap/Swig/commons.i index a9a170d2d41..2863fd9634e 100644 --- a/Wrap/Swig/commons.i +++ b/Wrap/Swig/commons.i @@ -14,12 +14,17 @@ %{ #define SWIG_FILE_WITH_INIT +/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` one can use + the Numpy Array API in multiple files for a single extension module. +*/ #define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY %} %include "numpy.i" %init %{ - import_array(); + if(!PyArray_API) { + import_array(); + } %} #define GCC_DIAG_OFF(x) -- GitLab From 9e97b46b51dd85c54c17459a6ee6ac1a5d73f060 Mon Sep 17 00:00:00 2001 From: "a.nejati.debug@debian-ci" <a.nejati.debug@debian-ci> Date: Tue, 9 May 2023 15:14:07 +0200 Subject: [PATCH 56/85] Update SWIG-produced wrappers for Python API --- auto/Wrap/libBornAgainBase_wrap.cpp | 7 ++++++- auto/Wrap/libBornAgainDevice_wrap.cpp | 7 ++++++- auto/Wrap/libBornAgainFit_wrap.cpp | 7 ++++++- auto/Wrap/libBornAgainParam_wrap.cpp | 7 ++++++- auto/Wrap/libBornAgainResample_wrap.cpp | 7 ++++++- auto/Wrap/libBornAgainSample_wrap.cpp | 7 ++++++- auto/Wrap/libBornAgainSim_wrap.cpp | 7 ++++++- 7 files changed, 42 insertions(+), 7 deletions(-) diff --git a/auto/Wrap/libBornAgainBase_wrap.cpp b/auto/Wrap/libBornAgainBase_wrap.cpp index 272622c7c94..e9cdd8146ce 100644 --- a/auto/Wrap/libBornAgainBase_wrap.cpp +++ b/auto/Wrap/libBornAgainBase_wrap.cpp @@ -4121,6 +4121,9 @@ SWIGINTERNINLINE PyObject* #define SWIG_FILE_WITH_INIT +/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` one can use + the Numpy Array API in multiple files for a single extension module. +*/ #define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY @@ -29293,7 +29296,9 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "SHARED_PTR_DISOWN",SWIG_From_int(static_cast< int >(0))); - import_array(); + if(!PyArray_API) { + import_array(); + } // thread safe initialization diff --git a/auto/Wrap/libBornAgainDevice_wrap.cpp b/auto/Wrap/libBornAgainDevice_wrap.cpp index 7f79e992872..735efdd2fba 100644 --- a/auto/Wrap/libBornAgainDevice_wrap.cpp +++ b/auto/Wrap/libBornAgainDevice_wrap.cpp @@ -4156,6 +4156,9 @@ SWIGINTERNINLINE PyObject* #define SWIG_FILE_WITH_INIT +/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` one can use + the Numpy Array API in multiple files for a single extension module. +*/ #define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY @@ -39245,7 +39248,9 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "SHARED_PTR_DISOWN",SWIG_From_int(static_cast< int >(0))); - import_array(); + if(!PyArray_API) { + import_array(); + } // thread safe initialization diff --git a/auto/Wrap/libBornAgainFit_wrap.cpp b/auto/Wrap/libBornAgainFit_wrap.cpp index 910c7a0f390..564bd7a6a68 100644 --- a/auto/Wrap/libBornAgainFit_wrap.cpp +++ b/auto/Wrap/libBornAgainFit_wrap.cpp @@ -4125,6 +4125,9 @@ SWIGINTERNINLINE PyObject* #define SWIG_FILE_WITH_INIT +/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` one can use + the Numpy Array API in multiple files for a single extension module. +*/ #define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY @@ -29489,7 +29492,9 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "SHARED_PTR_DISOWN",SWIG_From_int(static_cast< int >(0))); - import_array(); + if(!PyArray_API) { + import_array(); + } // thread safe initialization diff --git a/auto/Wrap/libBornAgainParam_wrap.cpp b/auto/Wrap/libBornAgainParam_wrap.cpp index a092f8b9d0b..e0553d7ef58 100644 --- a/auto/Wrap/libBornAgainParam_wrap.cpp +++ b/auto/Wrap/libBornAgainParam_wrap.cpp @@ -4124,6 +4124,9 @@ SWIGINTERNINLINE PyObject* #define SWIG_FILE_WITH_INIT +/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` one can use + the Numpy Array API in multiple files for a single extension module. +*/ #define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY @@ -32480,7 +32483,9 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "SHARED_PTR_DISOWN",SWIG_From_int(static_cast< int >(0))); - import_array(); + if(!PyArray_API) { + import_array(); + } // thread safe initialization diff --git a/auto/Wrap/libBornAgainResample_wrap.cpp b/auto/Wrap/libBornAgainResample_wrap.cpp index 1e369af586b..e9b9628aee8 100644 --- a/auto/Wrap/libBornAgainResample_wrap.cpp +++ b/auto/Wrap/libBornAgainResample_wrap.cpp @@ -4115,6 +4115,9 @@ SWIGINTERNINLINE PyObject* #define SWIG_FILE_WITH_INIT +/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` one can use + the Numpy Array API in multiple files for a single extension module. +*/ #define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY @@ -29100,7 +29103,9 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "SHARED_PTR_DISOWN",SWIG_From_int(static_cast< int >(0))); - import_array(); + if(!PyArray_API) { + import_array(); + } // thread safe initialization diff --git a/auto/Wrap/libBornAgainSample_wrap.cpp b/auto/Wrap/libBornAgainSample_wrap.cpp index b4e396d3f54..ad3af34a6ed 100644 --- a/auto/Wrap/libBornAgainSample_wrap.cpp +++ b/auto/Wrap/libBornAgainSample_wrap.cpp @@ -4224,6 +4224,9 @@ SWIGINTERNINLINE PyObject* #define SWIG_FILE_WITH_INIT +/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` one can use + the Numpy Array API in multiple files for a single extension module. +*/ #define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY @@ -61197,7 +61200,9 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "SHARED_PTR_DISOWN",SWIG_From_int(static_cast< int >(0))); - import_array(); + if(!PyArray_API) { + import_array(); + } // thread safe initialization diff --git a/auto/Wrap/libBornAgainSim_wrap.cpp b/auto/Wrap/libBornAgainSim_wrap.cpp index d9924e7b0be..08c00bb2fc4 100644 --- a/auto/Wrap/libBornAgainSim_wrap.cpp +++ b/auto/Wrap/libBornAgainSim_wrap.cpp @@ -4154,6 +4154,9 @@ SWIGINTERNINLINE PyObject* #define SWIG_FILE_WITH_INIT +/* NOTE: Using `PY_ARRAY_UNIQUE_SYMBOL` one can use + the Numpy Array API in multiple files for a single extension module. +*/ #define PY_ARRAY_UNIQUE_SYMBOL BORNAGAIN_PYTHONAPI_ARRAY @@ -37192,7 +37195,9 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "SHARED_PTR_DISOWN",SWIG_From_int(static_cast< int >(0))); - import_array(); + if(!PyArray_API) { + import_array(); + } // thread safe initialization -- GitLab From 73e7c501c8278d84419ad8235f1626eea7bb2569 Mon Sep 17 00:00:00 2001 From: "a.nejati.debug@debian-ci" <a.nejati.debug@debian-ci> Date: Wed, 10 May 2023 16:25:59 +0200 Subject: [PATCH 57/85] Apply clang-format --- PyCore/Embed/PythonInterpreter.cpp | 41 +++++++++++++++--------------- PyCore/Sample/ImportMultiLayer.cpp | 12 ++++----- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index 85458f7aee2..fcaa4b2b7d9 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -157,7 +157,8 @@ void PythonInterpreter::addPythonPath(const std::string& path) PyList_Append(sysPath, PyUnicode_FromString(path.c_str())); } - throw std::runtime_error("PythonInterpreter.addPythonPath: Cannot add to an empty Python path."); + throw std::runtime_error( + "PythonInterpreter.addPythonPath: Cannot add to an empty Python path."); } // Python stable ABI @@ -169,7 +170,6 @@ void PythonInterpreter::setPythonPath(const std::string& path) PythonInterpreter::checkError(); throw std::runtime_error("PythonInterpreter.setPythonPath: Cannot set the Python path."); } - } // Python stable ABI @@ -181,9 +181,9 @@ PyObjectPtr PythonInterpreter::import(const std::string& pymodule_name, const st PyObject* pymodule = PyImport_ImportModule(pymodule_name.c_str()); if (!pymodule || !PyModule_Check(pymodule)) { checkError(); - throw std::runtime_error( - errorDescription("PythonInterpreter: Cannot load Python module '" - + pymodule_name + "' (given path = '" + path + "')")); + throw std::runtime_error(errorDescription("PythonInterpreter: Cannot load Python module '" + + pymodule_name + "' (given path = '" + path + + "')")); } // returns a _new_ reference; ie. caller is responsible for the ref-count @@ -452,7 +452,7 @@ std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* "Conversion of Numpy array of dtype '" + std::to_string(npArray_dtype) + "' " - "to vector<double> is not implemented")); + "to vector<double> is not implemented")); } return data; @@ -600,9 +600,9 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio if (!npyArray_ptr) { checkError(); - throw std::runtime_error( - errorDescription("PythonInterpreter::Numpy: Cannot create a Numpy " - + std::to_string(n_dims) + "D-array from the given data")); + throw std::runtime_error(errorDescription("PythonInterpreter::Numpy: Cannot create a Numpy " + + std::to_string(n_dims) + + "D-array from the given data")); } return {npyArray_ptr}; @@ -644,7 +644,8 @@ PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) checkError(); throw std::runtime_error( errorDescription("PythonInterpreter: Cannot load 'bornagain' Python module " - "(given path = '" + path + "')")); + "(given path = '" + + path + "')")); } // restores single handler to make ctr-c alive. @@ -720,9 +721,8 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject // filename (Python string) and returns a Numpy array. if (!fabio_module.valid() || !PyModule_Check(fabio_module.get())) { - throw std::runtime_error( - errorDescription("PythonInterpreter.fabio: Invalid Python module " - "(expected 'fabio' module)")); + throw std::runtime_error(errorDescription("PythonInterpreter.fabio: Invalid Python module " + "(expected 'fabio' module)")); } PyObject* pFunc = PyObject_GetAttrString(fabio_module.get(), (char*)"open"); @@ -746,10 +746,10 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject Py_DecRef(pFunc); if (!pResult_open) { PythonInterpreter::checkError(); - std::runtime_error( - errorDescription("PythonInterpreter.fabio: " - "Invalid return value from calling the function " - "'fabio.open(\"" + filename + "\")'")); + std::runtime_error(errorDescription("PythonInterpreter.fabio: " + "Invalid return value from calling the function " + "'fabio.open(\"" + + filename + "\")'")); } // get `result.data` (must be a Numpy array) @@ -757,10 +757,9 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject Py_DecRef(pResult_open); if (!npyArray_ptr || !PyArray_Check(npyArray_ptr)) { PythonInterpreter::checkError(); - std::runtime_error( - errorDescription("PythonInterpreter.fabio: Invalid return value from " - "calling the function 'fabio.open(\"" - + filename + "\")' (expected a Numpy array)")); + std::runtime_error(errorDescription("PythonInterpreter.fabio: Invalid return value from " + "calling the function 'fabio.open(\"" + + filename + "\")' (expected a Numpy array)")); } // returns a _new_ reference; ie. caller is responsible for the ref-count diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index 7e015ff08af..57e15a633bd 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -20,10 +20,9 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& // locate the `get_simulation` function (it is an attribute of the module) PyObject* pAddFn = PyObject_GetAttrString(tmpModule.get(), functionName.c_str()); if (!pAddFn) { - throw std::runtime_error( - errorDescription("PythonInterpreter::BornAgain: " - "Cannot locate the compiled function '" - + functionName + "'")); + throw std::runtime_error(errorDescription("PythonInterpreter::BornAgain: " + "Cannot locate the compiled function '" + + functionName + "'")); } // create a `MultiLayer` Python object via calling the function @@ -33,9 +32,8 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& Py_DecRef(pAddFn); if (!instance) { - throw std::runtime_error( - errorDescription("PythonInterpreter::BornAgain: Cannot call the function '" - + functionName + "'")); + throw std::runtime_error(errorDescription( + "PythonInterpreter::BornAgain: Cannot call the function '" + functionName + "'")); } // Construct a C++ object from the Python object -- GitLab From 3b958a26e986e601bd303583626aaf51ed2d3f30 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 09:10:13 +0200 Subject: [PATCH 58/85] Doxy + PyCore --- Doc/Doxygen/core/Doxyfile.in | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/Doxygen/core/Doxyfile.in b/Doc/Doxygen/core/Doxyfile.in index 070fb3f9209..5bb7ae11cdf 100644 --- a/Doc/Doxygen/core/Doxyfile.in +++ b/Doc/Doxygen/core/Doxyfile.in @@ -899,6 +899,7 @@ WARN_LOGFILE = INPUT = @CMAKE_SOURCE_DIR@/Doc/Doxygen/core \ @CMAKE_SOURCE_DIR@/Doc/Doxygen/common \ + @CMAKE_SOURCE_DIR@/PyCore \ @CMAKE_SOURCE_DIR@/Base \ @CMAKE_SOURCE_DIR@/Fit \ @CMAKE_SOURCE_DIR@/Param \ -- GitLab From 17169e4713d43d1fa5bad51ac267d1d6dafc9c21 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 09:32:04 +0200 Subject: [PATCH 59/85] rm cpp conditional that depends on USER_API which is defined nowhere --- PyCore/Embed/PyCore.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/PyCore/Embed/PyCore.h b/PyCore/Embed/PyCore.h index 211857da2bf..a936b50e17c 100644 --- a/PyCore/Embed/PyCore.h +++ b/PyCore/Embed/PyCore.h @@ -16,7 +16,6 @@ #error no need to expose this header to Swig #endif -#ifndef USER_API #ifndef BORNAGAIN_PYTOOLS_PYCORE_H #define BORNAGAIN_PYTOOLS_PYCORE_H @@ -55,4 +54,3 @@ #endif // BORNAGAIN_PYTHON #endif // BORNAGAIN_PYTOOLS_PYCORE_H -#endif // USER_API -- GitLab From 4e43ec0aa7ae5a906b8c7df9087a91e6ef809f8b Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 09:33:56 +0200 Subject: [PATCH 60/85] PyObjectPtr: add/correct file header comment --- PyCore/Embed/PyObjectPtr.cpp | 14 ++++++++++++++ PyCore/Embed/PyObjectPtr.h | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/PyCore/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp index 6f91d8b395d..0999b33bcd6 100644 --- a/PyCore/Embed/PyObjectPtr.cpp +++ b/PyCore/Embed/PyObjectPtr.cpp @@ -1,3 +1,17 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file PyCore/Embed/PyObjectPtr.h +//! @brief Implements class PyObjectPtr +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + #include "PyObjectPtr.h" #include "PythonInterpreter.h" diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index b397a8f8066..2d9a446fc6b 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -3,7 +3,7 @@ // BornAgain: simulate and fit reflection and scattering // //! @file PyCore/Embed/PyObjectPtr.h -//! @brief Defines classes PyObjectPtr and Status. +//! @brief Defines class PyObjectPtr and struct NumpyArrayDescriptor //! //! @homepage http://www.bornagainproject.org //! @license GNU General Public License v3 or higher (see COPYING) -- GitLab From 983e7c8b7bc642613a94dad54a9d4cabdf316cec Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 09:38:14 +0200 Subject: [PATCH 61/85] rm unused NumpyArrayDescriptor --- PyCore/Embed/PyObjectPtr.h | 19 -------------- PyCore/Embed/PythonInterpreter.cpp | 42 ------------------------------ PyCore/Embed/PythonInterpreter.h | 3 --- 3 files changed, 64 deletions(-) diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 2d9a446fc6b..5971fd262ac 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -48,23 +48,4 @@ private: PyObject* m_ptr = nullptr; }; - -//! Numpy array descriptors -struct NumpyArrayDescriptor { - //! C-contiguous array flag - bool C_contiguous = false; - //! FORTRAN-contiguous array flag - bool F_contiguous = false; - //! Flag to indicate if the data area of the Numpy array is aligned and in machine byte-order - bool wellbehaved = false; - //! Number of dimensions - int n_dims = 0; - //! Sizes of dimensions - std::vector<np_size_t> dims; - //! Character code indicating the data type - char dtype = '\0'; - //! Flag to indicate whether the Numpy array owns its data - bool owns_data = false; -}; - #endif // BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index fcaa4b2b7d9..a862f905ec0 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -315,48 +315,6 @@ bool PythonInterpreter::Numpy::isInitialized() //---------------------------------------- -NumpyArrayDescriptor PythonInterpreter::Numpy::arrayDescriptor(PyObject* pyobject_ptr) -{ - if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { - throw( - std::runtime_error(errorDescription("PythonInterpreter::Numpy: Invalid Numpy array"))); - } - - PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; - PyArray_Descr* npArray_descr = PyArray_DTYPE(npArray_ptr); - // const int npArray_type = PyArray_TYPE(npArray_ptr); - const int npArray_ndim = PyArray_NDIM(npArray_ptr); - npy_intp* npArray_dims = PyArray_DIMS(npArray_ptr); - const int npArray_flags = PyArray_FLAGS(npArray_ptr); - // character code indicating the data type - const char npArray_dtype = npArray_descr->type; - - - NumpyArrayDescriptor np_descr_out; - np_descr_out.n_dims = npArray_ndim; - if (np_descr_out.n_dims > 0) { - np_descr_out.dims.resize(npArray_ndim); - for (npy_intp i_d = 0; i_d < npArray_ndim; ++i_d) - np_descr_out.dims[i_d] = npArray_dims[i_d]; - } - - np_descr_out.dtype = npArray_dtype; - - if (PyArray_IS_C_CONTIGUOUS(npArray_ptr)) - np_descr_out.C_contiguous = true; - - if (PyArray_IS_F_CONTIGUOUS(npArray_ptr)) - np_descr_out.F_contiguous = true; - - if (PyArray_ISBEHAVED_RO(npArray_ptr)) - np_descr_out.wellbehaved = true; - - if (npArray_flags & NPY_ARRAY_OWNDATA) - np_descr_out.owns_data = true; - - return np_descr_out; -} - std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) { if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index 38df1344f06..a8d2d51fa60 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -66,9 +66,6 @@ int initialize(); //! Checks if Numpy is initialized bool isInitialized(); -//! Obtains the descriptor of a Numpy array -NumpyArrayDescriptor arrayDescriptor(PyObject* pyobject_ptr); - //! Creates an empty Numpy array of type `double` with the given dimensions PyObjectPtr arrayND(std::vector<std::size_t>& dimensions); -- GitLab From 346365f5cdea281cb297c1da6c1dc5f94de08c8d Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 09:44:23 +0200 Subject: [PATCH 62/85] PythonInterpreter: standardize comments --- PyCore/Embed/PythonInterpreter.cpp | 31 +++++++++++++++++------------- PyCore/Embed/PythonInterpreter.h | 3 +-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index a862f905ec0..a0180e651b9 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -1,3 +1,17 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file PyCore/Embed/PythonInterpreter.h +//! @brief Implements functions to expose Python-interpreter functionality to C++. +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + /* Embedded Python Interpreter Note that Python objects are structures allocated on the heap, @@ -27,7 +41,6 @@ References: #include <string> #include <vector> - namespace { // thin wrapper to initialize Numpy @@ -104,7 +117,7 @@ inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, npy_intp rowI } } // namespace -//---------------------------------------- + // NOTE: "Python stable ABI" denotes the functions which use _only_ the Python's stable ABI; // see <https://docs.python.org/3/c-api/stable.html> @@ -112,11 +125,8 @@ inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, npy_intp rowI // Python stable ABI void PythonInterpreter::initialize() { - if (!Py_IsInitialized()) { + if (!Py_IsInitialized()) Py_Initialize(); - } - - // if Python interpreter is already initialized, do nothing } // Python stable ABI @@ -285,11 +295,9 @@ std::string PythonInterpreter::errorDescription(const std::string& title) return msg; } -//---------------------------------------- int PythonInterpreter::Numpy::initialize() { - // initialize Python C API, if needed PythonInterpreter::initialize(); @@ -313,8 +321,6 @@ bool PythonInterpreter::Numpy::isInitialized() return static_cast<bool>(PyArray_API); } -//---------------------------------------- - std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) { if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { @@ -582,7 +588,7 @@ double* PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) return data_ptr; } -//---------------------------------------- + PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) { @@ -665,14 +671,13 @@ std::vector<std::string> PythonInterpreter::BornAgain::listOfFunctions(const std return fn_names; } -//---------------------------------------- + PyObjectPtr PythonInterpreter::Fabio::import() { return {PythonInterpreter::import("fabio")}; } - PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObjectPtr& fabio_module) { // load an image via calling `fabio.load` function which takes a diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PythonInterpreter.h index a8d2d51fa60..627d9204050 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PythonInterpreter.h @@ -3,8 +3,7 @@ // BornAgain: simulate and fit reflection and scattering // //! @file PyCore/Embed/PythonInterpreter.h -//! @brief Definition of functions and classes to expose Python-interpreter functionality to -//! C++. +//! @brief Declares functions to expose Python-interpreter functionality to C++. //! //! @homepage http://www.bornagainproject.org //! @license GNU General Public License v3 or higher (see COPYING) -- GitLab From e79465a623cda2c538ddceda1dbfcf714f99b61a Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 09:44:56 +0200 Subject: [PATCH 63/85] rm duplicate includes --- PyCore/Embed/PythonInterpreter.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PythonInterpreter.cpp index a0180e651b9..923471d29f0 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PythonInterpreter.cpp @@ -32,14 +32,11 @@ References: #undef INCLUDE_NUMPY #include "PythonInterpreter.h" - #include <cstddef> // NULL #include <cstring> // memcpy #include <iostream> // cerr #include <memory> // unique_ptr #include <stdexcept> // runtime_error -#include <string> -#include <vector> namespace { -- GitLab From a289dce03e8d8d2b9d552b49d784a390deb56053 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 09:48:25 +0200 Subject: [PATCH 64/85] shorten name -> PyInterpreter --- Device/Data/Datafield.cpp | 8 +- GUI/View/Project/PyImportAssistant.cpp | 6 +- ...ythonInterpreter.cpp => PyInterpreter.cpp} | 166 +++++++++--------- .../{PythonInterpreter.h => PyInterpreter.h} | 12 +- PyCore/Embed/PyObjectPtr.cpp | 6 +- PyCore/Sample/ImportMultiLayer.cpp | 14 +- PyCore/Sample/ImportMultiLayer.h | 4 +- 7 files changed, 105 insertions(+), 111 deletions(-) rename PyCore/Embed/{PythonInterpreter.cpp => PyInterpreter.cpp} (77%) rename PyCore/Embed/{PythonInterpreter.h => PyInterpreter.h} (93%) diff --git a/Device/Data/Datafield.cpp b/Device/Data/Datafield.cpp index d79a326dbf1..cd22ebc69e9 100644 --- a/Device/Data/Datafield.cpp +++ b/Device/Data/Datafield.cpp @@ -205,13 +205,13 @@ Datafield* Datafield::crop(double xmin, double xmax) const #ifdef BORNAGAIN_PYTHON -#include "PyCore/Embed/PythonInterpreter.h" // Numpy::arrayND, Numpy::getDataPtr +#include "PyCore/Embed/PyInterpreter.h" // Numpy::arrayND, Numpy::getDataPtr PyObject* Datafield::npArray() const { // TODO: Thoroughly check this function regarding index manipulations - PythonInterpreter::Numpy::initialize(); + PyInterpreter::Numpy::initialize(); ASSERT(rank() <= 2); @@ -224,12 +224,12 @@ PyObject* Datafield::npArray() const std::swap(dimensions[0], dimensions[1]); // creating ndarray objects describing size of dimensions - PyObjectPtr pyarray{PythonInterpreter::Numpy::arrayND(dimensions)}; + PyObjectPtr pyarray{PyInterpreter::Numpy::arrayND(dimensions)}; if (!pyarray.valid()) return nullptr; // get the pointer to the data buffer of the array (assumed to be C-contiguous) - double* data{PythonInterpreter::Numpy::getDataPtr(pyarray.get())}; + double* data{PyInterpreter::Numpy::getDataPtr(pyarray.get())}; if (!data) return nullptr; diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index 169d4cdde7f..f360317bb4f 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -29,7 +29,7 @@ #include "GUI/View/Info/MessageBox.h" #include "GUI/View/Project/ProjectManager.h" #include "GUI/View/Tool/Globals.h" -#include "PyCore/Embed/PythonInterpreter.h" // listOfFunctions +#include "PyCore/Embed/PyInterpreter.h" // listOfFunctions #include "PyCore/Sample/ImportMultiLayer.h" // createMultiLayerFromPython #include "Sample/Multilayer/MultiLayer.h" #include <QApplication> @@ -104,7 +104,7 @@ std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QStri { QApplication::setOverrideCursor(Qt::WaitCursor); void* result_ptr = nullptr; - PyObjectPtr result{PythonInterpreter::createMultiLayerFromPython( + PyObjectPtr result{PyInterpreter::createMultiLayerFromPython( result_ptr, snippet.toStdString(), funcName.toStdString(), bornagainDir())}; std::unique_ptr<MultiLayer> multilayer_ptr(reinterpret_cast<MultiLayer*>(result_ptr)->clone()); @@ -153,7 +153,7 @@ QString getPySampleFunctionName(const QString& snippet) QApplication::setOverrideCursor(Qt::WaitCursor); std::vector<std::string> funcs_res{ - PythonInterpreter::BornAgain::listOfFunctions(snippet.toStdString(), bornagainDir())}; + PyInterpreter::BornAgain::listOfFunctions(snippet.toStdString(), bornagainDir())}; if (funcs_res.empty()) { QApplication::restoreOverrideCursor(); diff --git a/PyCore/Embed/PythonInterpreter.cpp b/PyCore/Embed/PyInterpreter.cpp similarity index 77% rename from PyCore/Embed/PythonInterpreter.cpp rename to PyCore/Embed/PyInterpreter.cpp index 923471d29f0..10bea670785 100644 --- a/PyCore/Embed/PythonInterpreter.cpp +++ b/PyCore/Embed/PyInterpreter.cpp @@ -2,7 +2,7 @@ // // BornAgain: simulate and fit reflection and scattering // -//! @file PyCore/Embed/PythonInterpreter.h +//! @file PyCore/Embed/PyInterpreter.cpp //! @brief Implements functions to expose Python-interpreter functionality to C++. //! //! @homepage http://www.bornagainproject.org @@ -31,7 +31,7 @@ References: #include "PyCore/Embed/PyCore.h" #undef INCLUDE_NUMPY -#include "PythonInterpreter.h" +#include "PyInterpreter.h" #include <cstddef> // NULL #include <cstring> // memcpy #include <iostream> // cerr @@ -79,16 +79,16 @@ std::vector<std::string> toVectorString(PyObject* py_object) if (PyTuple_Check(py_object)) { for (Py_ssize_t i = 0; i < PyTuple_Size(py_object); i++) { PyObject* value = PyTuple_GetItem(py_object, i); - result.push_back(PythonInterpreter::pyStrtoString(value)); + result.push_back(PyInterpreter::pyStrtoString(value)); } } else if (PyList_Check(py_object)) { for (Py_ssize_t i = 0; i < PyList_Size(py_object); i++) { PyObject* value = PyList_GetItem(py_object, i); - result.push_back(PythonInterpreter::pyStrtoString(value)); + result.push_back(PyInterpreter::pyStrtoString(value)); } } else { - throw std::runtime_error(PythonInterpreter::errorDescription( - "PythonInterpreter: Cannnot convert the given Python object " + throw std::runtime_error(PyInterpreter::errorDescription( + "PyInterpreter: Cannnot convert the given Python object " "to vector<string>.")); } @@ -120,20 +120,20 @@ inline void _realArray2DToDouble(PyArrayObject* const npArray_ptr, npy_intp rowI // see <https://docs.python.org/3/c-api/stable.html> // Python stable ABI -void PythonInterpreter::initialize() +void PyInterpreter::initialize() { if (!Py_IsInitialized()) Py_Initialize(); } // Python stable ABI -bool PythonInterpreter::isInitialized() +bool PyInterpreter::isInitialized() { return static_cast<bool>(Py_IsInitialized()); } // Python stable ABI -void PythonInterpreter::finalize() +void PyInterpreter::finalize() { // undo all initializations made by Py_Initialize() and subsequent use // of Python/C API functions, and destroy all sub-interpreters. @@ -142,11 +142,11 @@ void PythonInterpreter::finalize() } // Python stable ABI -bool PythonInterpreter::checkError() +bool PyInterpreter::checkError() { if (PyErr_Occurred()) { // print a standard traceback to sys.stderr and clear the error indicator - std::cerr << "---PythonInterpreter: Error in Python interpreter:\n"; + std::cerr << "---PyInterpreter: Error in Python interpreter:\n"; PyErr_Print(); std::cerr << "\n---\n"; return true; @@ -156,7 +156,7 @@ bool PythonInterpreter::checkError() } // Python stable ABI -void PythonInterpreter::addPythonPath(const std::string& path) +void PyInterpreter::addPythonPath(const std::string& path) { if (!path.empty()) { // add path to `PYTHONPATH` @@ -164,23 +164,22 @@ void PythonInterpreter::addPythonPath(const std::string& path) PyList_Append(sysPath, PyUnicode_FromString(path.c_str())); } - throw std::runtime_error( - "PythonInterpreter.addPythonPath: Cannot add to an empty Python path."); + throw std::runtime_error("PyInterpreter.addPythonPath: Cannot add to an empty Python path."); } // Python stable ABI -void PythonInterpreter::setPythonPath(const std::string& path) +void PyInterpreter::setPythonPath(const std::string& path) { // returns 0 on success, -1 on error const int result = PySys_SetObject((char*)"path", PyUnicode_FromString(path.c_str())); if (result != 0) { - PythonInterpreter::checkError(); - throw std::runtime_error("PythonInterpreter.setPythonPath: Cannot set the Python path."); + PyInterpreter::checkError(); + throw std::runtime_error("PyInterpreter.setPythonPath: Cannot set the Python path."); } } // Python stable ABI -PyObjectPtr PythonInterpreter::import(const std::string& pymodule_name, const std::string& path) +PyObjectPtr PyInterpreter::import(const std::string& pymodule_name, const std::string& path) { addPythonPath(path); @@ -188,7 +187,7 @@ PyObjectPtr PythonInterpreter::import(const std::string& pymodule_name, const st PyObject* pymodule = PyImport_ImportModule(pymodule_name.c_str()); if (!pymodule || !PyModule_Check(pymodule)) { checkError(); - throw std::runtime_error(errorDescription("PythonInterpreter: Cannot load Python module '" + throw std::runtime_error(errorDescription("PyInterpreter: Cannot load Python module '" + pymodule_name + "' (given path = '" + path + "')")); } @@ -198,12 +197,12 @@ PyObjectPtr PythonInterpreter::import(const std::string& pymodule_name, const st } // Python stable ABI -void PythonInterpreter::DecRef(PyObject* py_object) +void PyInterpreter::DecRef(PyObject* py_object) { Py_XDECREF(py_object); } -std::string PythonInterpreter::pyStrtoString(PyObject* py_object) +std::string PyInterpreter::pyStrtoString(PyObject* py_object) { std::string result; PyObject* pyStr = PyUnicode_AsEncodedString(py_object, "utf-8", "replace"); @@ -215,7 +214,7 @@ std::string PythonInterpreter::pyStrtoString(PyObject* py_object) return result; } -std::string PythonInterpreter::runtimeInfo() +std::string PyInterpreter::runtimeInfo() { std::string result; @@ -241,7 +240,7 @@ std::string PythonInterpreter::runtimeInfo() // Attempt to retrieve Python stack trace // Ref: <https://stackoverflow.com/q/1796510> -std::string PythonInterpreter::stackTrace() +std::string PyInterpreter::stackTrace() { std::string result; @@ -280,23 +279,23 @@ std::string PythonInterpreter::stackTrace() } result += "\n"; - result += PythonInterpreter::runtimeInfo(); + result += PyInterpreter::runtimeInfo(); result += "\n"; return result; } -std::string PythonInterpreter::errorDescription(const std::string& title) +std::string PyInterpreter::errorDescription(const std::string& title) { - std::string msg = title + "\n" + PythonInterpreter::stackTrace() + "\n"; + std::string msg = title + "\n" + PyInterpreter::stackTrace() + "\n"; return msg; } -int PythonInterpreter::Numpy::initialize() +int PyInterpreter::Numpy::initialize() { // initialize Python C API, if needed - PythonInterpreter::initialize(); + PyInterpreter::initialize(); int res; _init_numpy(res); @@ -305,7 +304,7 @@ int PythonInterpreter::Numpy::initialize() case 0: return res; case 1: - throw std::runtime_error(errorDescription("PythonInterpreter: Cannot initialize Numpy")); + throw std::runtime_error(errorDescription("PyInterpreter: Cannot initialize Numpy")); case 2: return res; } @@ -313,17 +312,16 @@ int PythonInterpreter::Numpy::initialize() return res; } -bool PythonInterpreter::Numpy::isInitialized() +bool PyInterpreter::Numpy::isInitialized() { return static_cast<bool>(PyArray_API); } -std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) +std::vector<double> PyInterpreter::Numpy::createVectorFromArray2D(PyObject* pyobject_ptr) { if (!pyobject_ptr || !PyArray_Check(pyobject_ptr)) { - throw(std::runtime_error( - errorDescription("PythonInterpreter::Numpy: Cannot convert an invalid " - "Numpy array to a C-Array"))); + throw(std::runtime_error(errorDescription("PyInterpreter::Numpy: Cannot convert an invalid " + "Numpy array to a C-Array"))); } PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; @@ -338,18 +336,17 @@ std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* // Numpy array type must be 2d if (npArray_ndim != 2) { - std::string msg = "PythonInterpreter::Numpy: Expected a Numpy 2d-array " + std::string msg = "PyInterpreter::Numpy: Expected a Numpy 2d-array " "(given number of dimensions is " + std::to_string(npArray_ndim) + ")"; - throw std::runtime_error( - errorDescription("PythonInterpreter::Numpy: Expected a Numpy 2d-array " - "(given number of dimensions is " - + std::to_string(npArray_ndim) + ")")); + throw std::runtime_error(errorDescription("PyInterpreter::Numpy: Expected a Numpy 2d-array " + "(given number of dimensions is " + + std::to_string(npArray_ndim) + ")")); } // Numpy array type must be numeric and real (eligible for casting to double) if (!PyDataType_ISNUMBER(npArray_descr) || PyDataType_ISCOMPLEX(npArray_descr)) { - std::string msg = "PythonInterpreter::Numpy: " + std::string msg = "PyInterpreter::Numpy: " "Expected a Numpy array of numeric type and real " "(given type '" + std::to_string(npArray_dtype) + "')"; @@ -409,7 +406,7 @@ std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* #endif // _WIN32 default: - throw std::runtime_error(errorDescription("PythonInterpreter::Numpy: " + throw std::runtime_error(errorDescription("PyInterpreter::Numpy: " "Conversion of Numpy array of dtype '" + std::to_string(npArray_dtype) + "' " @@ -419,17 +416,16 @@ std::vector<double> PythonInterpreter::Numpy::createVectorFromArray2D(PyObject* return data; } -PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, - const np_size_t dims[2]) +PyObjectPtr PyInterpreter::Numpy::createArray2DfromC(double* const c_array, const np_size_t dims[2]) { if (!c_array) { - throw std::runtime_error("PythonInterpreter::Numpy: " + throw std::runtime_error("PyInterpreter::Numpy: " "Cannot create a Numpy 1D-array from a null data pointer"); } const np_size_t size = dims[0] * dims[1]; if (size < 1) { - throw std::runtime_error("PythonInterpreter::Numpy: " + throw std::runtime_error("PyInterpreter::Numpy: " "Cannot create a Numpy 1D-array from a data with size = 0"); } @@ -439,7 +435,7 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 2, npDims, NPY_DOUBLE); if (!npArray_ptr) { checkError(); - throw std::runtime_error("PythonInterpreter::Numpy: " + throw std::runtime_error("PyInterpreter::Numpy: " "Cannot create a Numpy 1D-array from the " "given data (size = " + std::to_string(size) + ")"); @@ -457,17 +453,16 @@ PyObjectPtr PythonInterpreter::Numpy::createArray2DfromC(double* const c_array, return {npArray_ptr}; } -PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, - const np_size_t size) +PyObjectPtr PyInterpreter::Numpy::createArray1DfromC(double* const c_array, const np_size_t size) { if (!c_array) { - throw std::runtime_error("PythonInterpreter::Numpy: " + throw std::runtime_error("PyInterpreter::Numpy: " "Cannot create a Numpy 1D-array from a null data pointer"); } if (size < 1) { throw std::runtime_error( - errorDescription("PythonInterpreter::Numpy: " + errorDescription("PyInterpreter::Numpy: " "Cannot create a Numpy 1D-array from a data with size = 0")); } @@ -477,11 +472,11 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, PyObject* npArray_ptr = PyArray_SimpleNew(/* n_dims */ 1, npDims, NPY_DOUBLE); if (!npArray_ptr) { checkError(); - std::string msg = "PythonInterpreter::Numpy: " + std::string msg = "PyInterpreter::Numpy: " "Cannot create a Numpy 1D-array from the " "given data (size = " + std::to_string(size) + ")"; - throw std::runtime_error(errorDescription("PythonInterpreter::Numpy: " + throw std::runtime_error(errorDescription("PyInterpreter::Numpy: " "Cannot create a Numpy 1D-array from the " "given data (size = " + std::to_string(size) + ")")); @@ -500,18 +495,18 @@ PyObjectPtr PythonInterpreter::Numpy::createArray1DfromC(double* const c_array, } -PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const np_size_t dims[2]) +PyObjectPtr PyInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const np_size_t dims[2]) { if (!c_array) { throw std::runtime_error( - errorDescription("PythonInterpreter::Numpy: " + errorDescription("PyInterpreter::Numpy: " "Cannot create a Numpy 2D-array from a null data pointer")); } const np_size_t size = dims[0] * dims[1]; if (size < 1) { throw std::runtime_error( - errorDescription("PythonInterpreter::Numpy: " + errorDescription("PyInterpreter::Numpy: " "Cannot create a Numpy 2D-array from a data with size = 0")); } @@ -522,17 +517,16 @@ PyObjectPtr PythonInterpreter::Numpy::CArrayAsNpy2D(double* const c_array, const /* n_dims */ 2, npDims, NPY_DOUBLE, reinterpret_cast<void*>(c_array)); if (!npArray_ptr || !PyArray_Check(npArray_ptr)) { - PythonInterpreter::checkError(); - throw std::runtime_error( - errorDescription("PythonInterpreter::Numpy: Cannot convert the given " - "C-Array to a Numpy 2D-array")); + PyInterpreter::checkError(); + throw std::runtime_error(errorDescription("PyInterpreter::Numpy: Cannot convert the given " + "C-Array to a Numpy 2D-array")); } // returns a _new_ reference; ie. caller is responsible for the ref-count return {npArray_ptr}; } -PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensions) +PyObjectPtr PyInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensions) { // descriptors for the array dimensions const std::size_t n_dims = dimensions.size(); @@ -561,7 +555,7 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio if (!npyArray_ptr) { checkError(); - throw std::runtime_error(errorDescription("PythonInterpreter::Numpy: Cannot create a Numpy " + throw std::runtime_error(errorDescription("PyInterpreter::Numpy: Cannot create a Numpy " + std::to_string(n_dims) + "D-array from the given data")); } @@ -569,7 +563,7 @@ PyObjectPtr PythonInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensio return {npyArray_ptr}; } -double* PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) +double* PyInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) { PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; @@ -579,7 +573,7 @@ double* PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) if (!data_ptr) { checkError(); - throw(std::runtime_error(errorDescription("PythonInterpreter::Numpy: " + throw(std::runtime_error(errorDescription("PyInterpreter::Numpy: " "Numpy array has invalid data pointer"))); } @@ -587,9 +581,9 @@ double* PythonInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) } -PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) +PyObjectPtr PyInterpreter::BornAgain::import(const std::string& path) { - PythonInterpreter::addPythonPath(path); + PyInterpreter::addPythonPath(path); #ifndef _WIN32 // Stores signal handler before numpy's mess it up. @@ -604,7 +598,7 @@ PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) if (!ba_pymodule || !PyModule_Check(ba_pymodule)) { checkError(); throw std::runtime_error( - errorDescription("PythonInterpreter: Cannot load 'bornagain' Python module " + errorDescription("PyInterpreter: Cannot load 'bornagain' Python module " "(given path = '" + path + "')")); } @@ -617,10 +611,10 @@ PyObjectPtr PythonInterpreter::BornAgain::import(const std::string& path) return {ba_pymodule}; } -PyObjectPtr PythonInterpreter::BornAgain::importScript(const std::string& script, - const std::string& path) +PyObjectPtr PyInterpreter::BornAgain::importScript(const std::string& script, + const std::string& path) { - PyObjectPtr ba_pymodule{PythonInterpreter::BornAgain::import(path)}; + PyObjectPtr ba_pymodule{PyInterpreter::BornAgain::import(path)}; // TODO: Check ba module PyObject* pCompiledFn = Py_CompileString(script.c_str(), "", Py_file_input); @@ -641,15 +635,15 @@ PyObjectPtr PythonInterpreter::BornAgain::importScript(const std::string& script } -std::vector<std::string> PythonInterpreter::BornAgain::listOfFunctions(const std::string& script, - const std::string& path) +std::vector<std::string> PyInterpreter::BornAgain::listOfFunctions(const std::string& script, + const std::string& path) { - PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; + PyObjectPtr tmpModule{PyInterpreter::BornAgain::importScript(script, path)}; PyObject* pDict = PyModule_GetDict(tmpModule.get()); if (!pDict) { checkError(); - throw std::runtime_error("PythonInterpreter::BornAgain: " + throw std::runtime_error("PyInterpreter::BornAgain: " "Cannot obtain the dictionary from the script module"); } @@ -658,7 +652,7 @@ std::vector<std::string> PythonInterpreter::BornAgain::listOfFunctions(const std std::vector<std::string> fn_names; while (PyDict_Next(pDict, &pos, &key, &value)) { if (PyCallable_Check(value)) { - std::string func_name{PythonInterpreter::pyStrtoString(key)}; + std::string func_name{PyInterpreter::pyStrtoString(key)}; if (func_name.find("__") == std::string::npos) fn_names.push_back(func_name); } @@ -670,34 +664,34 @@ std::vector<std::string> PythonInterpreter::BornAgain::listOfFunctions(const std } -PyObjectPtr PythonInterpreter::Fabio::import() +PyObjectPtr PyInterpreter::Fabio::import() { - return {PythonInterpreter::import("fabio")}; + return {PyInterpreter::import("fabio")}; } -PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObjectPtr& fabio_module) +PyObjectPtr PyInterpreter::Fabio::open(const std::string& filename, PyObjectPtr& fabio_module) { // load an image via calling `fabio.load` function which takes a // filename (Python string) and returns a Numpy array. if (!fabio_module.valid() || !PyModule_Check(fabio_module.get())) { - throw std::runtime_error(errorDescription("PythonInterpreter.fabio: Invalid Python module " + throw std::runtime_error(errorDescription("PyInterpreter.fabio: Invalid Python module " "(expected 'fabio' module)")); } PyObject* pFunc = PyObject_GetAttrString(fabio_module.get(), (char*)"open"); if (!pFunc || !PyCallable_Check(pFunc)) { - PythonInterpreter::checkError(); + PyInterpreter::checkError(); throw std::runtime_error( - errorDescription("PythonInterpreter.fabio: The function 'fabio.open' is not callable")); + errorDescription("PyInterpreter.fabio: The function 'fabio.open' is not callable")); } // convert the filename to a Python unicode string PyObject* pFilename = PyUnicode_FromString(filename.c_str()); if (!pFilename) { - PythonInterpreter::checkError(); + PyInterpreter::checkError(); throw std::runtime_error( - errorDescription("PythonInterpreter.fabio: Filename '" + filename + errorDescription("PyInterpreter.fabio: Filename '" + filename + "' cannot be converted to Python unicode string")); } @@ -705,8 +699,8 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject PyObject* pResult_open = PyObject_CallFunctionObjArgs(pFunc, pFilename, NULL); Py_DecRef(pFunc); if (!pResult_open) { - PythonInterpreter::checkError(); - std::runtime_error(errorDescription("PythonInterpreter.fabio: " + PyInterpreter::checkError(); + std::runtime_error(errorDescription("PyInterpreter.fabio: " "Invalid return value from calling the function " "'fabio.open(\"" + filename + "\")'")); @@ -716,8 +710,8 @@ PyObjectPtr PythonInterpreter::Fabio::open(const std::string& filename, PyObject PyObject* npyArray_ptr = PyObject_GetAttrString(pResult_open, (char*)"data"); Py_DecRef(pResult_open); if (!npyArray_ptr || !PyArray_Check(npyArray_ptr)) { - PythonInterpreter::checkError(); - std::runtime_error(errorDescription("PythonInterpreter.fabio: Invalid return value from " + PyInterpreter::checkError(); + std::runtime_error(errorDescription("PyInterpreter.fabio: Invalid return value from " "calling the function 'fabio.open(\"" + filename + "\")' (expected a Numpy array)")); } diff --git a/PyCore/Embed/PythonInterpreter.h b/PyCore/Embed/PyInterpreter.h similarity index 93% rename from PyCore/Embed/PythonInterpreter.h rename to PyCore/Embed/PyInterpreter.h index 627d9204050..c3c3725f1b2 100644 --- a/PyCore/Embed/PythonInterpreter.h +++ b/PyCore/Embed/PyInterpreter.h @@ -2,7 +2,7 @@ // // BornAgain: simulate and fit reflection and scattering // -//! @file PyCore/Embed/PythonInterpreter.h +//! @file PyCore/Embed/PyInterpreter.h //! @brief Declares functions to expose Python-interpreter functionality to C++. //! //! @homepage http://www.bornagainproject.org @@ -12,14 +12,14 @@ // // ************************************************************************************************ -#ifndef BORNAGAIN_PYCORE_EMBED_PYTHONINTERPRETER_H -#define BORNAGAIN_PYCORE_EMBED_PYTHONINTERPRETER_H +#ifndef BORNAGAIN_PYCORE_EMBED_PYINTERPRETER_H +#define BORNAGAIN_PYCORE_EMBED_PYINTERPRETER_H #include "PyCore/Embed/PyObjectPtr.h" #include <string> #include <vector> -namespace PythonInterpreter { +namespace PyInterpreter { // Python stable ABI void initialize(); @@ -111,6 +111,6 @@ PyObjectPtr open(const std::string& filename, PyObjectPtr& fabio_module); } // namespace Fabio -} // namespace PythonInterpreter +} // namespace PyInterpreter -#endif // BORNAGAIN_PYCORE_EMBED_PYTHONINTERPRETER_H +#endif // BORNAGAIN_PYCORE_EMBED_PYINTERPRETER_H diff --git a/PyCore/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp index 0999b33bcd6..93e9530bd2b 100644 --- a/PyCore/Embed/PyObjectPtr.cpp +++ b/PyCore/Embed/PyObjectPtr.cpp @@ -13,7 +13,7 @@ // ************************************************************************************************ #include "PyObjectPtr.h" -#include "PythonInterpreter.h" +#include "PyInterpreter.h" #include <stdexcept> // runtime_error @@ -53,13 +53,13 @@ void PyObjectPtr::reset() void PyObjectPtr::discard() { - if (!PythonInterpreter::isInitialized()) { + if (!PyInterpreter::isInitialized()) { throw(std::runtime_error("Decrementing Python reference-count without " "Python initialized leads to memory access violation " "(segmentation fault)")); } - PythonInterpreter::DecRef(m_ptr); + PyInterpreter::DecRef(m_ptr); reset(); } diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index 57e15a633bd..c538008ee88 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -2,7 +2,7 @@ #include "ImportMultiLayer.h" #include "PyCore/Embed/PyCore.h" -#include "PyCore/Embed/PythonInterpreter.h" // BornAgain::importScript +#include "PyCore/Embed/PyInterpreter.h" // BornAgain::importScript // SWIG runtime access for creating a MultiLayer instance from Python #include "auto/Wrap/swig_runtime.h" @@ -10,17 +10,17 @@ #include <stdexcept> // runtime_error -namespace PythonInterpreter { +namespace PyInterpreter { PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& script, const std::string& functionName, const std::string& path) { - PyObjectPtr tmpModule{PythonInterpreter::BornAgain::importScript(script, path)}; + PyObjectPtr tmpModule{PyInterpreter::BornAgain::importScript(script, path)}; // locate the `get_simulation` function (it is an attribute of the module) PyObject* pAddFn = PyObject_GetAttrString(tmpModule.get(), functionName.c_str()); if (!pAddFn) { - throw std::runtime_error(errorDescription("PythonInterpreter::BornAgain: " + throw std::runtime_error(errorDescription("PyInterpreter::BornAgain: " "Cannot locate the compiled function '" + functionName + "'")); } @@ -33,7 +33,7 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& if (!instance) { throw std::runtime_error(errorDescription( - "PythonInterpreter::BornAgain: Cannot call the function '" + functionName + "'")); + "PyInterpreter::BornAgain: Cannot call the function '" + functionName + "'")); } // Construct a C++ object from the Python object @@ -48,7 +48,7 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& if (!SWIG_IsOK(res)) { Py_DecRef(instance); throw std::runtime_error( - errorDescription("PythonInterpreter::BornAgain: SWIG failed to extract a " + errorDescription("PyInterpreter::BornAgain: SWIG failed to extract a " "'MultiLayer' instance via calling the function '" + functionName + "'")); } @@ -57,6 +57,6 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& return {instance}; } -} // namespace PythonInterpreter +} // namespace PyInterpreter #endif // BORNAGAIN_PYTHON diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index 97aa9127fd7..bac5057da48 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -24,7 +24,7 @@ class MultiLayer; -namespace PythonInterpreter { +namespace PyInterpreter { //! Creates a multi layer by running python code in embedded interpreter, given //! The Python-script filename and the name of the function which produces a `MultiLayer`. @@ -37,6 +37,6 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& const std::string& functionName, const std::string& path = ""); -} // namespace PythonInterpreter +} // namespace PyInterpreter #endif // BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H -- GitLab From ce04f7836965e3f3491a0019590bea0c61c4608c Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 09:54:01 +0200 Subject: [PATCH 65/85] rm unused include --- PyCore/Embed/PyObjectPtr.h | 1 - 1 file changed, 1 deletion(-) diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 5971fd262ac..6ff4232b60d 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -16,7 +16,6 @@ #define BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H #include "PyCore/Embed/PyObjectDecl.h" -#include <vector> //! Safe container for PyObjects: //! The class `PyObjectPtr` contains a `PyObject*` (or `NULL`). -- GitLab From 906c33411317a51ca18be2ed7728adb257832774 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 09:57:58 +0200 Subject: [PATCH 66/85] get && reset => release --- PyCore/Embed/PyObjectPtr.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/PyCore/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp index 93e9530bd2b..6c0cfb60716 100644 --- a/PyCore/Embed/PyObjectPtr.cpp +++ b/PyCore/Embed/PyObjectPtr.cpp @@ -28,10 +28,8 @@ PyObjectPtr::~PyObjectPtr() } PyObjectPtr::PyObjectPtr(PyObjectPtr&& other) - : PyObjectPtr(other.get()) + : PyObjectPtr(other.release()) { - // reset the moved object - other.reset(); } PyObject* PyObjectPtr::get() -- GitLab From 94247bc1afdf1e3a7910549d4f6d8360f99fe3c7 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 10:03:45 +0200 Subject: [PATCH 67/85] code formatting --- PyCore/Embed/PyInterpreter.cpp | 4 +--- PyCore/Embed/PyInterpreter.h | 2 ++ PyCore/Embed/PyObjectPtr.cpp | 3 +-- PyCore/Embed/PyObjectPtr.h | 1 - PyCore/Sample/ImportMultiLayer.cpp | 1 - 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/PyCore/Embed/PyInterpreter.cpp b/PyCore/Embed/PyInterpreter.cpp index 10bea670785..d62cb345cb1 100644 --- a/PyCore/Embed/PyInterpreter.cpp +++ b/PyCore/Embed/PyInterpreter.cpp @@ -230,9 +230,8 @@ std::string PyInterpreter::runtimeInfo() PyObject* sysPath = PySys_GetObject((char*)"path"); std::vector<std::string> content{toVectorString(sysPath)}; result += "sys.path: "; - for (const std::string& s : content) { + for (const std::string& s : content) result += s + ","; - } result += "\n"; return result; @@ -565,7 +564,6 @@ PyObjectPtr PyInterpreter::Numpy::arrayND(std::vector<std::size_t>& dimensions) double* PyInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) { - PyArrayObject* npArray_ptr{reinterpret_cast<PyArrayObject*>(pyobject_ptr)}; // get the pointer to data buffer of the Numpy array diff --git a/PyCore/Embed/PyInterpreter.h b/PyCore/Embed/PyInterpreter.h index c3c3725f1b2..55fc1e1af42 100644 --- a/PyCore/Embed/PyInterpreter.h +++ b/PyCore/Embed/PyInterpreter.h @@ -90,6 +90,7 @@ PyObjectPtr CArrayAsNpy2D(double* const c_array, const np_size_t dims[2]); // BornAgain-related functionality namespace BornAgain { + //! Imports BornAgain from given location. If path is empty, tries to rely on PYTHONPATH. PyObjectPtr import(const std::string& path = ""); @@ -104,6 +105,7 @@ std::vector<std::string> listOfFunctions(const std::string& script, const std::s // Fabio-related functionality namespace Fabio { + //! Imports 'fabio' Python module PyObjectPtr import(); //! Calls 'fabio.open' on a given file (needs Numpy) diff --git a/PyCore/Embed/PyObjectPtr.cpp b/PyCore/Embed/PyObjectPtr.cpp index 6c0cfb60716..34363bcf4ce 100644 --- a/PyCore/Embed/PyObjectPtr.cpp +++ b/PyCore/Embed/PyObjectPtr.cpp @@ -51,11 +51,10 @@ void PyObjectPtr::reset() void PyObjectPtr::discard() { - if (!PyInterpreter::isInitialized()) { + if (!PyInterpreter::isInitialized()) throw(std::runtime_error("Decrementing Python reference-count without " "Python initialized leads to memory access violation " "(segmentation fault)")); - } PyInterpreter::DecRef(m_ptr); reset(); diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 6ff4232b60d..5e3ff54c48e 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -22,7 +22,6 @@ //! Decrementing Python reference-count is performed automatically when //! the life-time of a `PyObjectPtr` expires. class PyObjectPtr { - public: PyObjectPtr(PyObject* pyobject_ptr); ~PyObjectPtr(); diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index c538008ee88..8eedcb35e8d 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -9,7 +9,6 @@ #include <stdexcept> // runtime_error - namespace PyInterpreter { PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& script, -- GitLab From 9ee786373c291d875014f7deefcb834cbbcee11c Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 10:14:05 +0200 Subject: [PATCH 68/85] define np_size_t where it is used --- PyCore/Embed/PyInterpreter.h | 2 ++ PyCore/Embed/PyObjectDecl.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PyCore/Embed/PyInterpreter.h b/PyCore/Embed/PyInterpreter.h index 55fc1e1af42..d4c8267838c 100644 --- a/PyCore/Embed/PyInterpreter.h +++ b/PyCore/Embed/PyInterpreter.h @@ -59,6 +59,8 @@ std::string errorDescription(const std::string& title = ""); //-- Numpy-related functionality namespace Numpy { +typedef long int np_size_t; // size type used in Numpy + //! Initializes Numpy int initialize(); diff --git a/PyCore/Embed/PyObjectDecl.h b/PyCore/Embed/PyObjectDecl.h index c1af8b2a02d..a2b52e87e0b 100644 --- a/PyCore/Embed/PyObjectDecl.h +++ b/PyCore/Embed/PyObjectDecl.h @@ -22,6 +22,4 @@ typedef _object PyObject; #endif // PyObject_HEAD -typedef long int np_size_t; // size type used in Numpy - #endif // BORNAGAIN_PYCORE_EMBED_PYOBJECTDECL_H -- GitLab From 7c62ff14d770d76cdb14195383c43fcd9defd701 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 10:19:59 +0200 Subject: [PATCH 69/85] ImportMultilayer: add/simplify file header --- PyCore/Sample/ImportMultiLayer.cpp | 20 ++++++++++++++++---- PyCore/Sample/ImportMultiLayer.h | 7 +++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index 8eedcb35e8d..271cc81d46d 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -1,3 +1,17 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file PyCore/Sample/ImportMultiLayer.h +//! @brief Implements createMultiLayerFromPython. +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + #ifdef BORNAGAIN_PYTHON #include "ImportMultiLayer.h" @@ -18,11 +32,10 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& // locate the `get_simulation` function (it is an attribute of the module) PyObject* pAddFn = PyObject_GetAttrString(tmpModule.get(), functionName.c_str()); - if (!pAddFn) { + if (!pAddFn) throw std::runtime_error(errorDescription("PyInterpreter::BornAgain: " "Cannot locate the compiled function '" + functionName + "'")); - } // create a `MultiLayer` Python object via calling the function PyObject* instance = PyObject_CallFunctionObjArgs(pAddFn, NULL); @@ -30,10 +43,9 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& // clean up Py_DecRef(pAddFn); - if (!instance) { + if (!instance) throw std::runtime_error(errorDescription( "PyInterpreter::BornAgain: Cannot call the function '" + functionName + "'")); - } // Construct a C++ object from the Python object /* NOTE: diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index bac5057da48..76c06749c31 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -3,7 +3,7 @@ // BornAgain: simulate and fit reflection and scattering // //! @file PyCore/Sample/ImportMultiLayer.h -//! @brief Forward declaration for createMultiLayerFromPython. +//! @brief Declares createMultiLayerFromPython. //! //! @homepage http://www.bornagainproject.org //! @license GNU General Public License v3 or higher (see COPYING) @@ -12,12 +12,11 @@ // // ************************************************************************************************ -#ifndef BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H -#define BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H - #ifdef SWIG #error no need to expose this header to Swig #endif +#ifndef BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H +#define BORNAGAIN_PYCORE_SAMPLE_IMPORTMULTILAYER_H #include "PyCore/Embed/PyObjectPtr.h" #include <string> -- GitLab From e4fc54291a067fd16182ce183163fd23477c9ee9 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 10:21:22 +0200 Subject: [PATCH 70/85] correct include --- PyCore/Sample/ImportMultiLayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index 271cc81d46d..dd02da7f350 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -14,7 +14,7 @@ #ifdef BORNAGAIN_PYTHON -#include "ImportMultiLayer.h" +#include "PyCore/Sample/ImportMultiLayer.h" #include "PyCore/Embed/PyCore.h" #include "PyCore/Embed/PyInterpreter.h" // BornAgain::importScript -- GitLab From 10110d86ec63caf0a754d0de9afcaac9aec3712e Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 12:58:10 +0200 Subject: [PATCH 71/85] rm blank line --- PyCore/Sample/ImportMultiLayer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index dd02da7f350..61333b45312 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -20,7 +20,6 @@ // SWIG runtime access for creating a MultiLayer instance from Python #include "auto/Wrap/swig_runtime.h" - #include <stdexcept> // runtime_error namespace PyInterpreter { -- GitLab From 9b4f35d2a132cb5903b5637a64af801e8e17d902 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 13:25:55 +0200 Subject: [PATCH 72/85] correct include; assert that added path is not empty --- PyCore/Embed/PyInterpreter.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/PyCore/Embed/PyInterpreter.cpp b/PyCore/Embed/PyInterpreter.cpp index d62cb345cb1..0e4c4024bb4 100644 --- a/PyCore/Embed/PyInterpreter.cpp +++ b/PyCore/Embed/PyInterpreter.cpp @@ -31,7 +31,8 @@ References: #include "PyCore/Embed/PyCore.h" #undef INCLUDE_NUMPY -#include "PyInterpreter.h" +#include "PyCore/Embed/PyInterpreter.h" +#include "Base/Util/Assert.h" #include <cstddef> // NULL #include <cstring> // memcpy #include <iostream> // cerr @@ -151,20 +152,15 @@ bool PyInterpreter::checkError() std::cerr << "\n---\n"; return true; } - return false; } // Python stable ABI void PyInterpreter::addPythonPath(const std::string& path) { - if (!path.empty()) { - // add path to `PYTHONPATH` - PyObject* sysPath = PySys_GetObject((char*)"path"); - PyList_Append(sysPath, PyUnicode_FromString(path.c_str())); - } - - throw std::runtime_error("PyInterpreter.addPythonPath: Cannot add to an empty Python path."); + ASSERT(!path.empty()); + PyObject* sysPath = PySys_GetObject((char*)"path"); + PyList_Append(sysPath, PyUnicode_FromString(path.c_str())); // add to `PYTHONPATH` } // Python stable ABI -- GitLab From ae2004d59798705226c8a15a06fbbcfa6122493b Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 13:33:11 +0200 Subject: [PATCH 73/85] correct logic for addPythonPath --- PyCore/Embed/PyInterpreter.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/PyCore/Embed/PyInterpreter.cpp b/PyCore/Embed/PyInterpreter.cpp index 0e4c4024bb4..07b970bc83f 100644 --- a/PyCore/Embed/PyInterpreter.cpp +++ b/PyCore/Embed/PyInterpreter.cpp @@ -177,7 +177,9 @@ void PyInterpreter::setPythonPath(const std::string& path) // Python stable ABI PyObjectPtr PyInterpreter::import(const std::string& pymodule_name, const std::string& path) { - addPythonPath(path); + ASSERT(!pymodule_name.empty()); + if(!path.empty()) + addPythonPath(path); // import the module PyObject* pymodule = PyImport_ImportModule(pymodule_name.c_str()); @@ -577,7 +579,8 @@ double* PyInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) PyObjectPtr PyInterpreter::BornAgain::import(const std::string& path) { - PyInterpreter::addPythonPath(path); + if(!path.empty()) + PyInterpreter::addPythonPath(path); #ifndef _WIN32 // Stores signal handler before numpy's mess it up. -- GitLab From 9e5da4cccc5d7165f18a74aea7577ae4f98f93f5 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 14:37:32 +0200 Subject: [PATCH 74/85] rm unnessary cast --- PyCore/Embed/PyInterpreter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyCore/Embed/PyInterpreter.cpp b/PyCore/Embed/PyInterpreter.cpp index 07b970bc83f..8a73b0e2d35 100644 --- a/PyCore/Embed/PyInterpreter.cpp +++ b/PyCore/Embed/PyInterpreter.cpp @@ -159,7 +159,7 @@ bool PyInterpreter::checkError() void PyInterpreter::addPythonPath(const std::string& path) { ASSERT(!path.empty()); - PyObject* sysPath = PySys_GetObject((char*)"path"); + PyObject* sysPath = PySys_GetObject("path"); PyList_Append(sysPath, PyUnicode_FromString(path.c_str())); // add to `PYTHONPATH` } -- GitLab From 7a25045fb92aefdd137cce7e7d3e5df20638b2c7 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 17:14:51 +0200 Subject: [PATCH 75/85] add call to initialize() => resolved segfault --- PyCore/Embed/PyInterpreter.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/PyCore/Embed/PyInterpreter.cpp b/PyCore/Embed/PyInterpreter.cpp index 8a73b0e2d35..3873f5d8af2 100644 --- a/PyCore/Embed/PyInterpreter.cpp +++ b/PyCore/Embed/PyInterpreter.cpp @@ -178,6 +178,9 @@ void PyInterpreter::setPythonPath(const std::string& path) PyObjectPtr PyInterpreter::import(const std::string& pymodule_name, const std::string& path) { ASSERT(!pymodule_name.empty()); + + PyInterpreter::Numpy::initialize(); + if(!path.empty()) addPythonPath(path); @@ -583,15 +586,16 @@ PyObjectPtr PyInterpreter::BornAgain::import(const std::string& path) PyInterpreter::addPythonPath(path); #ifndef _WIN32 - // Stores signal handler before numpy's mess it up. - // This is to make ctrl-c working from terminal. - - PyOS_sighandler_t sighandler; - sighandler = PyOS_getsig(SIGINT); + // store ctrl-C handler before Numpy messes it up + PyOS_sighandler_t sighandler = PyOS_getsig(SIGINT); #endif PyObject* ba_pymodule = PyImport_ImportModule("bornagain"); +#ifndef _WIN32 + PyOS_setsig(SIGINT, sighandler); // restore previous ctrl-C handler +#endif + if (!ba_pymodule || !PyModule_Check(ba_pymodule)) { checkError(); throw std::runtime_error( @@ -600,11 +604,6 @@ PyObjectPtr PyInterpreter::BornAgain::import(const std::string& path) + path + "')")); } - // restores single handler to make ctr-c alive. -#ifndef _WIN32 - PyOS_setsig(SIGINT, sighandler); -#endif - return {ba_pymodule}; } @@ -635,6 +634,8 @@ PyObjectPtr PyInterpreter::BornAgain::importScript(const std::string& script, std::vector<std::string> PyInterpreter::BornAgain::listOfFunctions(const std::string& script, const std::string& path) { + PyInterpreter::Numpy::initialize(); + PyObjectPtr tmpModule{PyInterpreter::BornAgain::importScript(script, path)}; PyObject* pDict = PyModule_GetDict(tmpModule.get()); -- GitLab From 345780833984fe388b6438f5d961718700509cf0 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 11:19:44 +0200 Subject: [PATCH 76/85] no need to clone --- GUI/View/Project/PyImportAssistant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index f360317bb4f..a27b7dc9443 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -107,7 +107,7 @@ std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QStri PyObjectPtr result{PyInterpreter::createMultiLayerFromPython( result_ptr, snippet.toStdString(), funcName.toStdString(), bornagainDir())}; - std::unique_ptr<MultiLayer> multilayer_ptr(reinterpret_cast<MultiLayer*>(result_ptr)->clone()); + std::unique_ptr<MultiLayer> multilayer_ptr(reinterpret_cast<MultiLayer*>(result_ptr)); if (!result.valid()) { QApplication::restoreOverrideCursor(); -- GitLab From f37079618d0ecd68ec72efd262cd7460a94efeae Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 17:39:49 +0200 Subject: [PATCH 77/85] expand auto --- GUI/View/Project/PyImportAssistant.cpp | 6 +++--- GUI/View/SampleDesigner/SampleListModel.cpp | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index a27b7dc9443..db33105cc4a 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -107,7 +107,7 @@ std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QStri PyObjectPtr result{PyInterpreter::createMultiLayerFromPython( result_ptr, snippet.toStdString(), funcName.toStdString(), bornagainDir())}; - std::unique_ptr<MultiLayer> multilayer_ptr(reinterpret_cast<MultiLayer*>(result_ptr)); + std::unique_ptr<MultiLayer> multilayer_ptr(reinterpret_cast<MultiLayer*>(result_ptr)->clone()); if (!result.valid()) { QApplication::restoreOverrideCursor(); @@ -184,9 +184,9 @@ std::unique_ptr<MultiLayer> PyImportAssistant::importMultiLayer() if (funcName.isEmpty()) return {}; - auto sample = createMultiLayer(snippet, funcName); + std::unique_ptr<MultiLayer> sample = createMultiLayer(snippet, funcName); if (!sample) - return {}; + throw std::runtime_error("Import did not yield MultiLayer object"); if (sample->sampleName() == "Unnamed") sample->setSampleName(GUI::Util::Path::baseName(fileName).toStdString()); diff --git a/GUI/View/SampleDesigner/SampleListModel.cpp b/GUI/View/SampleDesigner/SampleListModel.cpp index 304e0d788ee..fc297ae116c 100644 --- a/GUI/View/SampleDesigner/SampleListModel.cpp +++ b/GUI/View/SampleDesigner/SampleListModel.cpp @@ -162,14 +162,13 @@ QModelIndex SampleListModel::createSampleFromExamples(const QString& className, #ifdef BORNAGAIN_PYTHON QModelIndex SampleListModel::createSampleFromPython() { - auto sample = PyImportAssistant::importMultiLayer(); + std::unique_ptr<MultiLayer> sample = PyImportAssistant::importMultiLayer(); if (!sample) - return {}; // any messages already shown to user; no dlg necessary anymore + return {}; // any messages already shown to user - - auto* sampleItem = PyImportAssistant::itemizeSample(*sample); + SampleItem* sampleItem = PyImportAssistant::itemizeSample(*sample); if (!sampleItem) - return {}; // any messages already shown to user; no dlg necessary anymore + return {}; // any messages already shown to user sampleItem->setDescription("Imported from python code"); -- GitLab From d6cb4dc133c4d4a25bd0325891e7399e7e79598e Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 17:53:35 +0200 Subject: [PATCH 78/85] avoid duplicate statement --- GUI/View/Project/PyImportAssistant.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index db33105cc4a..44b38b4e114 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -155,14 +155,14 @@ QString getPySampleFunctionName(const QString& snippet) std::vector<std::string> funcs_res{ PyInterpreter::BornAgain::listOfFunctions(snippet.toStdString(), bornagainDir())}; + QApplication::restoreOverrideCursor(); + if (funcs_res.empty()) { - QApplication::restoreOverrideCursor(); QString message("Exception thrown while acquiring functions from Python code.\n\n"); DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, "").exec(); return ""; } - QApplication::restoreOverrideCursor(); QStringList funcList{GUI::Util::String::fromStdStrings(funcs_res)}; return selectPySampleFunction(funcList); } -- GitLab From 7efd8db501e4d69a37512b1c06827c7b95cf59aa Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 17:55:10 +0200 Subject: [PATCH 79/85] disambiguate local fct name --- GUI/View/Project/PyImportAssistant.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index 44b38b4e114..ffc44cf87aa 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -100,7 +100,7 @@ QString readFile(const QString& fileName) //! Creates a multi-layer by executing a funcName in embedded Python. //! Function is supposed to be in code provided by 'snippet'. -std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QString& funcName) +std::unique_ptr<MultiLayer> code2sample(const QString& snippet, const QString& funcName) { QApplication::setOverrideCursor(Qt::WaitCursor); void* result_ptr = nullptr; @@ -184,7 +184,7 @@ std::unique_ptr<MultiLayer> PyImportAssistant::importMultiLayer() if (funcName.isEmpty()) return {}; - std::unique_ptr<MultiLayer> sample = createMultiLayer(snippet, funcName); + std::unique_ptr<MultiLayer> sample = code2sample(snippet, funcName); if (!sample) throw std::runtime_error("Import did not yield MultiLayer object"); -- GitLab From 5349be9379663fa31b39e0a3c76e0a8276991877 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 17:58:57 +0200 Subject: [PATCH 80/85] err msg --- PyCore/Sample/ImportMultiLayer.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index 61333b45312..d0f00e36546 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -58,9 +58,7 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& if (!SWIG_IsOK(res)) { Py_DecRef(instance); throw std::runtime_error( - errorDescription("PyInterpreter::BornAgain: SWIG failed to extract a " - "'MultiLayer' instance via calling the function '" - + functionName + "'")); + errorDescription("Call of '" + functionName + "' did not yield MultiLayer instance")); } multilayer_ptr = reinterpret_cast<void*>(argp1); -- GitLab From 2036ae0b77c76a027c1c91d6b6e95a3563b53a4e Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 18:20:49 +0200 Subject: [PATCH 81/85] simplify result checking and error handling --- GUI/View/Project/PyImportAssistant.cpp | 14 +++---------- PyCore/Embed/PyInterpreter.cpp | 6 +++--- PyCore/Sample/ImportMultiLayer.cpp | 27 ++++++++++++-------------- PyCore/Sample/ImportMultiLayer.h | 5 ++--- 4 files changed, 20 insertions(+), 32 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index ffc44cf87aa..67b1703db1a 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -103,21 +103,13 @@ QString readFile(const QString& fileName) std::unique_ptr<MultiLayer> code2sample(const QString& snippet, const QString& funcName) { QApplication::setOverrideCursor(Qt::WaitCursor); - void* result_ptr = nullptr; - PyObjectPtr result{PyInterpreter::createMultiLayerFromPython( - result_ptr, snippet.toStdString(), funcName.toStdString(), bornagainDir())}; - std::unique_ptr<MultiLayer> multilayer_ptr(reinterpret_cast<MultiLayer*>(result_ptr)->clone()); - - if (!result.valid()) { - QApplication::restoreOverrideCursor(); - QString message("Exception thrown while executing Python code to create sample.\n\n"); - DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, "").exec(); - } + void* result_ptr = PyInterpreter::createMultiLayerFromPython( + snippet.toStdString(), funcName.toStdString(), bornagainDir()); QApplication::restoreOverrideCursor(); - return multilayer_ptr; + return std::unique_ptr<MultiLayer>{reinterpret_cast<MultiLayer*>(result_ptr)->clone()}; } //! Lets user select a function name which generates a MultiLayer. diff --git a/PyCore/Embed/PyInterpreter.cpp b/PyCore/Embed/PyInterpreter.cpp index 3873f5d8af2..a8a977fc454 100644 --- a/PyCore/Embed/PyInterpreter.cpp +++ b/PyCore/Embed/PyInterpreter.cpp @@ -31,8 +31,8 @@ References: #include "PyCore/Embed/PyCore.h" #undef INCLUDE_NUMPY -#include "PyCore/Embed/PyInterpreter.h" #include "Base/Util/Assert.h" +#include "PyCore/Embed/PyInterpreter.h" #include <cstddef> // NULL #include <cstring> // memcpy #include <iostream> // cerr @@ -181,7 +181,7 @@ PyObjectPtr PyInterpreter::import(const std::string& pymodule_name, const std::s PyInterpreter::Numpy::initialize(); - if(!path.empty()) + if (!path.empty()) addPythonPath(path); // import the module @@ -582,7 +582,7 @@ double* PyInterpreter::Numpy::getDataPtr(PyObject* pyobject_ptr) PyObjectPtr PyInterpreter::BornAgain::import(const std::string& path) { - if(!path.empty()) + if (!path.empty()) PyInterpreter::addPythonPath(path); #ifndef _WIN32 diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index d0f00e36546..d6f9674de09 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -24,8 +24,8 @@ namespace PyInterpreter { -PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& script, - const std::string& functionName, const std::string& path) +void* createMultiLayerFromPython(const std::string& script, const std::string& functionName, + const std::string& path) { PyObjectPtr tmpModule{PyInterpreter::BornAgain::importScript(script, path)}; @@ -37,32 +37,29 @@ PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& + functionName + "'")); // create a `MultiLayer` Python object via calling the function - PyObject* instance = PyObject_CallFunctionObjArgs(pAddFn, NULL); + PyObject* ret1 = PyObject_CallFunctionObjArgs(pAddFn, NULL); // clean up Py_DecRef(pAddFn); - if (!instance) - throw std::runtime_error(errorDescription( - "PyInterpreter::BornAgain: Cannot call the function '" + functionName + "'")); + if (!ret1) + throw std::runtime_error( + errorDescription("Failed execting Python function '" + functionName + "'")); - // Construct a C++ object from the Python object - /* NOTE: - This conversion requires a SWIG-produced Python API. - */ + // Construct a C++ object from the Python object. + // This conversion requires a SWIG-produced Python API. void* argp1 = 0; swig_type_info* pTypeInfo = SWIG_TypeQuery("MultiLayer *"); - const int res = SWIG_ConvertPtr(instance, &argp1, pTypeInfo, 0); + const int res = SWIG_ConvertPtr(ret1, &argp1, pTypeInfo, 0); if (!SWIG_IsOK(res)) { - Py_DecRef(instance); + Py_DecRef(ret1); throw std::runtime_error( - errorDescription("Call of '" + functionName + "' did not yield MultiLayer instance")); + errorDescription("Function " + functionName + " did not yield MultiLayer instance")); } - multilayer_ptr = reinterpret_cast<void*>(argp1); - return {instance}; + return reinterpret_cast<void*>(argp1); } } // namespace PyInterpreter diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index 76c06749c31..c8065a3900a 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -32,9 +32,8 @@ namespace PyInterpreter { // `MultiLayer` instance in C++ (memory should be managed by the caller). //! The returned value is the PyObject corresponding the intermediate _Python_ instance of //! of the `MultiLayer` class. -PyObjectPtr createMultiLayerFromPython(void*& multilayer_ptr, const std::string& script, - const std::string& functionName, - const std::string& path = ""); +void* createMultiLayerFromPython(const std::string& script, const std::string& functionName, + const std::string& path = ""); } // namespace PyInterpreter -- GitLab From 92064858a0ae00f2e11044c1123bb163efc5c3d3 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 18:23:31 +0200 Subject: [PATCH 82/85] rm fwd decl --- PyCore/Sample/ImportMultiLayer.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index c8065a3900a..58b30b7ef99 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -21,8 +21,6 @@ #include "PyCore/Embed/PyObjectPtr.h" #include <string> -class MultiLayer; - namespace PyInterpreter { //! Creates a multi layer by running python code in embedded interpreter, given -- GitLab From 206e0830514fa460dc603e8fdaf0731586519456 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 18:31:08 +0200 Subject: [PATCH 83/85] PyInterpreter::pyscript2object no longer knows about MultiLayer --- GUI/View/Project/PyImportAssistant.cpp | 6 +++--- PyCore/Sample/ImportMultiLayer.cpp | 16 +++++----------- PyCore/Sample/ImportMultiLayer.h | 12 +++--------- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/GUI/View/Project/PyImportAssistant.cpp b/GUI/View/Project/PyImportAssistant.cpp index 67b1703db1a..7f03fd1375f 100644 --- a/GUI/View/Project/PyImportAssistant.cpp +++ b/GUI/View/Project/PyImportAssistant.cpp @@ -104,12 +104,12 @@ std::unique_ptr<MultiLayer> code2sample(const QString& snippet, const QString& f { QApplication::setOverrideCursor(Qt::WaitCursor); - void* result_ptr = PyInterpreter::createMultiLayerFromPython( - snippet.toStdString(), funcName.toStdString(), bornagainDir()); + void* pObj = PyInterpreter::pyscript2object(snippet.toStdString(), funcName.toStdString(), + "MultiLayer", bornagainDir()); QApplication::restoreOverrideCursor(); - return std::unique_ptr<MultiLayer>{reinterpret_cast<MultiLayer*>(result_ptr)->clone()}; + return std::unique_ptr<MultiLayer>{reinterpret_cast<MultiLayer*>(pObj)->clone()}; } //! Lets user select a function name which generates a MultiLayer. diff --git a/PyCore/Sample/ImportMultiLayer.cpp b/PyCore/Sample/ImportMultiLayer.cpp index d6f9674de09..6a35d88beae 100644 --- a/PyCore/Sample/ImportMultiLayer.cpp +++ b/PyCore/Sample/ImportMultiLayer.cpp @@ -17,15 +17,11 @@ #include "PyCore/Sample/ImportMultiLayer.h" #include "PyCore/Embed/PyCore.h" #include "PyCore/Embed/PyInterpreter.h" // BornAgain::importScript +#include "auto/Wrap/swig_runtime.h" // for creating a core object from Python +#include <stdexcept> -// SWIG runtime access for creating a MultiLayer instance from Python -#include "auto/Wrap/swig_runtime.h" -#include <stdexcept> // runtime_error - -namespace PyInterpreter { - -void* createMultiLayerFromPython(const std::string& script, const std::string& functionName, - const std::string& path) +void* PyInterpreter::pyscript2object(const std::string& script, const std::string& functionName, + const std::string& typeName, const std::string& path) { PyObjectPtr tmpModule{PyInterpreter::BornAgain::importScript(script, path)}; @@ -50,7 +46,7 @@ void* createMultiLayerFromPython(const std::string& script, const std::string& f // This conversion requires a SWIG-produced Python API. void* argp1 = 0; - swig_type_info* pTypeInfo = SWIG_TypeQuery("MultiLayer *"); + swig_type_info* pTypeInfo = SWIG_TypeQuery((typeName + "*").c_str()); const int res = SWIG_ConvertPtr(ret1, &argp1, pTypeInfo, 0); if (!SWIG_IsOK(res)) { @@ -62,6 +58,4 @@ void* createMultiLayerFromPython(const std::string& script, const std::string& f return reinterpret_cast<void*>(argp1); } -} // namespace PyInterpreter - #endif // BORNAGAIN_PYTHON diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index 58b30b7ef99..8e58e4f40d4 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -23,15 +23,9 @@ namespace PyInterpreter { -//! Creates a multi layer by running python code in embedded interpreter, given -//! The Python-script filename and the name of the function which produces a `MultiLayer`. -//! A path can be given to import `BornAgain` library (if empty, relies on `PYTHONPATH`). -//! Finally, if successful, `multilayer_ptr` will be a void pointer to the resulting -// `MultiLayer` instance in C++ (memory should be managed by the caller). -//! The returned value is the PyObject corresponding the intermediate _Python_ instance of -//! of the `MultiLayer` class. -void* createMultiLayerFromPython(const std::string& script, const std::string& functionName, - const std::string& path = ""); +//! Creates a core object by running python code in embedded interpreter. +void* pyscript2object(const std::string& script, const std::string& functionName, + const std::string& typeName, const std::string& path = ""); } // namespace PyInterpreter -- GitLab From 85b372a0cc958511fa5a6f79bb808c70d20b2e22 Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 22:15:53 +0200 Subject: [PATCH 84/85] don't include PyCore unless BORNAGAIN_PYTHON is defined --- PyCore/Embed/PyCore.h | 6 +++--- PyCore/Embed/PyInterpreter.h | 3 +++ PyCore/Embed/PyObjectDecl.h | 3 +++ PyCore/Embed/PyObjectPtr.h | 3 +++ PyCore/Sample/ImportMultiLayer.h | 3 +++ Tests/Functional/Consistence/CMakeLists.txt | 1 + Tests/Functional/CoreSpecial/CMakeLists.txt | 1 + Tests/Functional/Fitting/CMakeLists.txt | 1 + Tests/Suite/GUI/CMakeLists.txt | 1 + Tests/Suite/Persist/CMakeLists.txt | 1 + Tests/Suite/Py/CMakeLists.txt | 1 + Tests/Unit/Device/CMakeLists.txt | 1 + Tests/Unit/GUI/CMakeLists.txt | 1 + Tests/Unit/Numeric/CMakeLists.txt | 1 + Tests/Unit/Sim/CMakeLists.txt | 1 + 15 files changed, 25 insertions(+), 3 deletions(-) diff --git a/PyCore/Embed/PyCore.h b/PyCore/Embed/PyCore.h index a936b50e17c..a6461ed2494 100644 --- a/PyCore/Embed/PyCore.h +++ b/PyCore/Embed/PyCore.h @@ -12,14 +12,15 @@ // // ************************************************************************************************ +#ifndef BORNAGAIN_PYTHON +#error this header requires Python support +#endif #ifdef SWIG #error no need to expose this header to Swig #endif - #ifndef BORNAGAIN_PYTOOLS_PYCORE_H #define BORNAGAIN_PYTOOLS_PYCORE_H -#ifdef BORNAGAIN_PYTHON #undef _POSIX_C_SOURCE #undef _XOPEN_SOURCE @@ -52,5 +53,4 @@ #pragma GCC diagnostic pop #endif -#endif // BORNAGAIN_PYTHON #endif // BORNAGAIN_PYTOOLS_PYCORE_H diff --git a/PyCore/Embed/PyInterpreter.h b/PyCore/Embed/PyInterpreter.h index d4c8267838c..318918a9af7 100644 --- a/PyCore/Embed/PyInterpreter.h +++ b/PyCore/Embed/PyInterpreter.h @@ -12,6 +12,9 @@ // // ************************************************************************************************ +#ifndef BORNAGAIN_PYTHON +#error this header requires Python support +#endif #ifndef BORNAGAIN_PYCORE_EMBED_PYINTERPRETER_H #define BORNAGAIN_PYCORE_EMBED_PYINTERPRETER_H diff --git a/PyCore/Embed/PyObjectDecl.h b/PyCore/Embed/PyObjectDecl.h index a2b52e87e0b..7c778a01786 100644 --- a/PyCore/Embed/PyObjectDecl.h +++ b/PyCore/Embed/PyObjectDecl.h @@ -12,6 +12,9 @@ // // ************************************************************************************************ +#ifndef BORNAGAIN_PYTHON +#error this header requires Python support +#endif #ifndef BORNAGAIN_PYCORE_EMBED_PYOBJECTDECL_H #define BORNAGAIN_PYCORE_EMBED_PYOBJECTDECL_H diff --git a/PyCore/Embed/PyObjectPtr.h b/PyCore/Embed/PyObjectPtr.h index 5e3ff54c48e..10ee85b1a8c 100644 --- a/PyCore/Embed/PyObjectPtr.h +++ b/PyCore/Embed/PyObjectPtr.h @@ -12,6 +12,9 @@ // // ************************************************************************************************ +#ifndef BORNAGAIN_PYTHON +#error this header requires Python support +#endif #ifndef BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H #define BORNAGAIN_PYCORE_EMBED_PYOBJECTPTR_H diff --git a/PyCore/Sample/ImportMultiLayer.h b/PyCore/Sample/ImportMultiLayer.h index 8e58e4f40d4..d7d3be30da1 100644 --- a/PyCore/Sample/ImportMultiLayer.h +++ b/PyCore/Sample/ImportMultiLayer.h @@ -12,6 +12,9 @@ // // ************************************************************************************************ +#ifndef BORNAGAIN_PYTHON +#error this header requires Python support +#endif #ifdef SWIG #error no need to expose this header to Swig #endif diff --git a/Tests/Functional/Consistence/CMakeLists.txt b/Tests/Functional/Consistence/CMakeLists.txt index e1a35c3b984..46021b1f4ef 100644 --- a/Tests/Functional/Consistence/CMakeLists.txt +++ b/Tests/Functional/Consistence/CMakeLists.txt @@ -5,6 +5,7 @@ set(test TestCoreConsistence) file(GLOB source_files *.cpp) add_executable(${test} ${source_files} ${CMAKE_SOURCE_DIR}/Tests/GTestWrapper/TestAll.cpp) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_include_directories(${test} PUBLIC ${BornAgainSim_INCLUDE_DIRS} diff --git a/Tests/Functional/CoreSpecial/CMakeLists.txt b/Tests/Functional/CoreSpecial/CMakeLists.txt index 930eec5cb1a..07339ff72f3 100644 --- a/Tests/Functional/CoreSpecial/CMakeLists.txt +++ b/Tests/Functional/CoreSpecial/CMakeLists.txt @@ -8,6 +8,7 @@ set(source_files ) add_executable(${test} ${source_files} ${CMAKE_SOURCE_DIR}/Tests/GTestWrapper/TestAll.cpp) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_include_directories(${test} PUBLIC ${CMAKE_SOURCE_DIR}/Tests/Unit/utilities diff --git a/Tests/Functional/Fitting/CMakeLists.txt b/Tests/Functional/Fitting/CMakeLists.txt index 4c0b06fa5b5..c014c872a3d 100644 --- a/Tests/Functional/Fitting/CMakeLists.txt +++ b/Tests/Functional/Fitting/CMakeLists.txt @@ -24,6 +24,7 @@ set(test TestCoreFitting) file(GLOB source_files *.cpp) add_executable(${test} ${source_files} ${CMAKE_SOURCE_DIR}/Tests/GTestWrapper/TestAll.cpp) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_include_directories(${test} PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/common/gtest/gtest-1.8.0/include) target_link_libraries(${test} BornAgainTestSimFactory gtest) diff --git a/Tests/Suite/GUI/CMakeLists.txt b/Tests/Suite/GUI/CMakeLists.txt index 8492fc6481b..b3a6dd58683 100644 --- a/Tests/Suite/GUI/CMakeLists.txt +++ b/Tests/Suite/GUI/CMakeLists.txt @@ -28,6 +28,7 @@ set(test TestGuiSuite) set(source_files Check.cpp ../Common/RunTest.cpp ${CMAKE_SOURCE_DIR}/Tests/GTestWrapper/TestAll.cpp) add_executable(${test} ${source_files}) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_compile_options(${test} PUBLIC -DGUI_STD_TEST -DTESTNAME=GUI) target_compile_definitions(${test} PUBLIC DISABLE_DISTRIBUTION_TESTS) diff --git a/Tests/Suite/Persist/CMakeLists.txt b/Tests/Suite/Persist/CMakeLists.txt index 2990110f7dc..f7ebcf6d248 100644 --- a/Tests/Suite/Persist/CMakeLists.txt +++ b/Tests/Suite/Persist/CMakeLists.txt @@ -28,6 +28,7 @@ set(test TestSuitePersist) set(source_files Check.cpp ../Common/RunTest.cpp ${CMAKE_SOURCE_DIR}/Tests/GTestWrapper/TestAll.cpp) add_executable(${test} ${source_files}) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_compile_definitions(${test} PUBLIC DISABLE_DISTRIBUTION_TESTS -DTESTNAME=Persist) target_include_directories(${test} PUBLIC ${BornAgainSim_INCLUDE_DIRS} diff --git a/Tests/Suite/Py/CMakeLists.txt b/Tests/Suite/Py/CMakeLists.txt index 65fcb717d4c..0fce5374e12 100644 --- a/Tests/Suite/Py/CMakeLists.txt +++ b/Tests/Suite/Py/CMakeLists.txt @@ -31,6 +31,7 @@ set(source_files Check.cpp ${CMAKE_SOURCE_DIR}/Tests/GTestWrapper/TestAll.cpp) add_executable(${test} ${source_files}) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_compile_options(${test} PUBLIC -DPYTHON_STD_TEST -DTESTNAME=Py) target_compile_definitions(${test} PUBLIC DISABLE_DISTRIBUTION_TESTS) diff --git a/Tests/Unit/Device/CMakeLists.txt b/Tests/Unit/Device/CMakeLists.txt index c9711e1bd45..4af951753db 100644 --- a/Tests/Unit/Device/CMakeLists.txt +++ b/Tests/Unit/Device/CMakeLists.txt @@ -5,6 +5,7 @@ set(test UnitTestDevice) file(GLOB source_files "*.cpp" ${CMAKE_SOURCE_DIR}/Tests/GTestWrapper/TestAll.cpp) add_executable(${test} ${source_files}) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_link_libraries(${test} BornAgainDevice gtest) gtest_discover_tests(${test} DISCOVERY_TIMEOUT 300 TEST_PREFIX Unit.Device.) diff --git a/Tests/Unit/GUI/CMakeLists.txt b/Tests/Unit/GUI/CMakeLists.txt index 7017c7e7675..9b5ddab89e1 100644 --- a/Tests/Unit/GUI/CMakeLists.txt +++ b/Tests/Unit/GUI/CMakeLists.txt @@ -11,6 +11,7 @@ include_directories(${CMAKE_SOURCE_DIR}/Tests/Unit/utilities) set(CMAKE_AUTOMOC ON) add_executable(${test} ${source_files}) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_link_libraries(${test} BornAgainGUI gtest Qt6::Core Qt6::Test) set(test_data_dir ${TEST_OUTPUT_DIR}/Unit/GUI) diff --git a/Tests/Unit/Numeric/CMakeLists.txt b/Tests/Unit/Numeric/CMakeLists.txt index c84a1136d29..7dbe1e4e392 100644 --- a/Tests/Unit/Numeric/CMakeLists.txt +++ b/Tests/Unit/Numeric/CMakeLists.txt @@ -9,6 +9,7 @@ endif() file(GLOB source_files "*.cpp" ${CMAKE_SOURCE_DIR}/Tests/GTestWrapper/TestAll.cpp) add_executable(${test} ${source_files}) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_include_directories(${test} PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/common/gtest/gtest-1.8.0/include ${CMAKE_SOURCE_DIR}/Tests/Unit/utilities) diff --git a/Tests/Unit/Sim/CMakeLists.txt b/Tests/Unit/Sim/CMakeLists.txt index 4e85ac25487..984b2549076 100644 --- a/Tests/Unit/Sim/CMakeLists.txt +++ b/Tests/Unit/Sim/CMakeLists.txt @@ -5,6 +5,7 @@ set(test UnitTestSim) file(GLOB source_files "*.cpp" ${CMAKE_SOURCE_DIR}/Tests/GTestWrapper/TestAll.cpp) add_executable(${test} ${source_files}) +target_compile_definitions(${test} PRIVATE -DBORNAGAIN_PYTHON) target_link_libraries(${test} BornAgainSim gtest) gtest_discover_tests(${test} DISCOVERY_TIMEOUT 300 TEST_PREFIX Unit.Sim.) -- GitLab From 762362539aa298c74b4034f2db57251819a7494d Mon Sep 17 00:00:00 2001 From: "Joachim Wuttke (h)" <j.wuttke@fz-juelich.de> Date: Mon, 15 May 2023 22:29:17 +0200 Subject: [PATCH 85/85] restore build with -DBORNAGAIN_PYTHON=OFF -DBA_GUI=OFF --- CMakeLists.txt | 4 +++- Device/CMakeLists.txt | 7 ++++-- Device/Data/Datafield.h | 2 ++ GUI/CMakeLists.txt | 4 +++- cmake/BornAgain/MakeLib.cmake | 45 ++++++++++++++++++----------------- 5 files changed, 36 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b59ddabb0f2..0f30fcec2b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -262,7 +262,9 @@ if(BA_TESTS) endif() # documentation -add_subdirectory(hugo) +if(BORNAGAIN_PYTHON) # required for examples + add_subdirectory(hugo) +endif() add_subdirectory(Doc/man) if(CONFIGURE_DOXY) add_subdirectory(Doc/Doxygen) diff --git a/Device/CMakeLists.txt b/Device/CMakeLists.txt index d0159f8470e..61245dd0311 100644 --- a/Device/CMakeLists.txt +++ b/Device/CMakeLists.txt @@ -27,14 +27,17 @@ MakeLib(${lib}) target_link_libraries(${lib} PUBLIC BornAgainBase - BornAgainPyCore BornAgainFit BornAgainParam BornAgainSample BornAgainResample ${CMAKE_THREAD_LIBS_INIT} ${tspectrum_LIBRARY} - ) +) +if(BORNAGAIN_PYTHON) + target_link_libraries(${lib} PUBLIC BornAgainPyCore) +endif() + target_include_directories(${lib} PUBLIC ${CMAKE_SOURCE_DIR} diff --git a/Device/Data/Datafield.h b/Device/Data/Datafield.h index 9acb6ba3f72..53e53560465 100644 --- a/Device/Data/Datafield.h +++ b/Device/Data/Datafield.h @@ -18,7 +18,9 @@ #include <memory> #include <vector> +#ifdef BORNAGAIN_PYTHON #include "PyCore/Embed/PyObjectDecl.h" +#endif using std::size_t; diff --git a/GUI/CMakeLists.txt b/GUI/CMakeLists.txt index 5c5c40fc54a..f69e8b2b6fc 100644 --- a/GUI/CMakeLists.txt +++ b/GUI/CMakeLists.txt @@ -51,7 +51,6 @@ endif() target_link_libraries(${lib} PUBLIC - BornAgainPyCore BornAgainSim BornAgainImg3D Qt6::Widgets @@ -62,6 +61,9 @@ target_link_libraries(${lib} Qt6::PrintSupport qcustomplot ) +if(BORNAGAIN_PYTHON) + target_link_libraries(${lib} PUBLIC BornAgainPyCore) +endif() target_include_directories(${lib} PUBLIC ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/GUI/qcustomplot diff --git a/cmake/BornAgain/MakeLib.cmake b/cmake/BornAgain/MakeLib.cmake index ecf4520b05d..86ad2b519c2 100644 --- a/cmake/BornAgain/MakeLib.cmake +++ b/cmake/BornAgain/MakeLib.cmake @@ -64,31 +64,32 @@ function(MakeLib lib) if(BORNAGAIN_PYTHON) target_compile_definitions(${lib} PRIVATE -DBORNAGAIN_PYTHON) - endif() - # SWIG-produced interface - if(swigtmpdir) - SwigLib(${name} ${lib} ${swigtmpdir}) - endif() + # SWIG-produced interface + if(swigtmpdir) + SwigLib(${name} ${lib} ${swigtmpdir}) + endif() - # Python package - if(BA_PY_PACKAGE) - # add the BornAgain library to the Python wheel - add_library_to_wheel(${lib}) - endif() + # Python package + if(BA_PY_PACKAGE) + # add the BornAgain library to the Python wheel + add_library_to_wheel(${lib}) + endif() - if(WIN32 AND BORNAGAIN_PYTHON) - # python in windows required .pyd extension for the library name - install(FILES ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${libprefix}${lib}.pyd - DESTINATION ${destination_python} COMPONENT Libraries) - add_custom_command( - TARGET ${lib} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_BINARY_DIR}/bin/${libprefix}${lib}${libsuffix} - ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${libprefix}${lib}${libsuffix} - ) - endif() + if(WIN32) + # python in windows required .pyd extension for the library name + install(FILES ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${libprefix}${lib}.pyd + DESTINATION ${destination_python} COMPONENT Libraries) + add_custom_command( + TARGET ${lib} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/bin/${libprefix}${lib}${libsuffix} + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${libprefix}${lib}${libsuffix} + ) + endif() + + endif() # BORNAGAIN_PYTHON # installation install(TARGETS ${lib} -- GitLab