preCICE v3.1.2
Loading...
Searching...
No Matches
PythonAction.cpp
Go to the documentation of this file.
1#ifndef PRECICE_NO_PYTHON
2
3#include "PythonAction.hpp"
4#include <Eigen/Core>
5#include <Python.h>
6#include <cstdlib>
7#include <filesystem>
8#include <memory>
9#include <ostream>
10#include <pthread.h>
11#include <string>
12#include <utility>
13
14#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
15#include <numpy/arrayobject.h>
16
17#include "logging/LogMacros.hpp"
18#include "mesh/Data.hpp"
19#include "mesh/Mesh.hpp"
20#include "utils/String.hpp"
21#include "utils/assertion.hpp"
22
23namespace precice::action {
24
25namespace {
26std::string python_error_as_string()
27{
28 PyObject *ptype, *pvalue, *ptraceback;
29 PyErr_Fetch(&ptype, &pvalue, &ptraceback);
30 if (ptype == nullptr) {
31 return "<no error available>";
32 } else {
33 // pvalue and ptraceback may be NULL
34 // We don't need the type or the traceback, so we dereference them straight away
35 Py_DECREF(ptype);
36 Py_XDECREF(ptraceback); // may be NULL
37
38 if (pvalue == nullptr) {
39 return "<no error message available>";
40 }
41 wchar_t *wmessage = PyUnicode_AsWideCharString(pvalue, nullptr);
42 Py_DECREF(pvalue);
43
44 if (wmessage) {
45 auto message = utils::truncate_wstring_to_string(wmessage);
46 PyMem_Free(wmessage);
47 return message;
48 } else {
49 return "<fetching error message failed>";
50 }
51 }
52}
53} // namespace
54
56 Timing timing,
57 std::string modulePath,
58 std::string moduleName,
59 const mesh::PtrMesh &mesh,
60 int targetDataID,
61 int sourceDataID)
62 : Action(timing, mesh),
63 _modulePath(std::move(modulePath)),
64 _moduleName(std::move(moduleName))
65{
67 "The module path of the python action \"{}\" does not exist. The configured path is \"{}\".",
69 if (targetDataID != -1) {
70 _targetData = getMesh()->data(targetDataID);
72 }
73 if (sourceDataID != -1) {
74 _sourceData = getMesh()->data(sourceDataID);
76 }
77}
78
80{
81 if (_module != nullptr) {
83 PRECICE_ASSERT(_module != nullptr);
84 Py_DECREF(_moduleNameObject);
85 Py_DECREF(_module);
86 Py_Finalize();
87 }
88}
89
91{
93
94 if (not _isInitialized) {
95 initialize();
96 }
97
98 PyObject *dataArgs = PyTuple_New(_numberArguments);
99
100 int i = 0;
101 for (auto &targetStample : _targetData->stamples()) { // iterate over _targetData, because it must always exist
102 PRECICE_ASSERT(_targetData); // _targetData is mandatory, cannot call setSampleAtTime, if target data is not provided!
103
104 PyObject *pythonTime = PyFloat_FromDouble(targetStample.timestamp);
105 PyTuple_SetItem(dataArgs, 0, pythonTime);
106
107 if (_sourceData) { // _sourceData is optional
108 auto &sourceStample = _sourceData->stamples()[i]; // simultaneously iterate over _targetData->stamples()
109 _sourceData->values() = sourceStample.sample.values; // put data into temporary buffer
110 PRECICE_CHECK(math::equals(sourceStample.timestamp, targetStample.timestamp), "Trying to perform python action on samples with different timestamps: {} for source data and {} for target data. Time mesh of source data and target data must agree.", sourceStample.timestamp, targetStample.timestamp);
111 i++;
112 npy_intp sourceDim[] = {_sourceData->values().size()};
113 double * sourceValues = _sourceData->values().data();
114 _sourceValues = PyArray_SimpleNewFromData(1, sourceDim, NPY_DOUBLE, sourceValues);
115 PRECICE_CHECK(_sourceValues != nullptr, "Creating python source values failed. Please check that the source data name is used by the mesh in action:python.");
116 PyTuple_SetItem(dataArgs, 1, _sourceValues);
117 }
118
119 _targetData->values() = targetStample.sample.values; // put data into temporary buffer
120 npy_intp targetDim[] = {_targetData->values().size()};
121 double * targetValues = _targetData->values().data();
122 // PRECICE_ASSERT(_targetValues == NULL);
123 _targetValues = PyArray_SimpleNewFromData(1, targetDim, NPY_DOUBLE, targetValues);
124 PRECICE_CHECK(_targetValues != nullptr, "Creating python target values failed. Please check that the target data name is used by the mesh in action:python.");
125 int argumentIndex = _sourceData ? 2 : 1;
126 PyTuple_SetItem(dataArgs, argumentIndex, _targetValues);
127
128 PyObject_CallObject(_performAction, dataArgs);
129 PRECICE_CHECK(!PyErr_Occurred(),
130 "Error occurred during call of function performAction() in python module \"{}\". "
131 "The error message is: {}",
132 _moduleName, python_error_as_string());
133
134 _targetData->setSampleAtTime(targetStample.timestamp, _targetData->sample());
135 }
136 Py_DECREF(dataArgs);
137}
138
140{
142 // Initialize Python
143 Py_Initialize();
145 // Append execution path to find module to import
146 PyRun_SimpleString("import sys");
147 std::string appendPathCommand("sys.path.append('" + _modulePath + "')");
148 PyRun_SimpleString(appendPathCommand.c_str());
149 _moduleNameObject = PyUnicode_FromString(_moduleName.c_str());
150 _module = PyImport_Import(_moduleNameObject);
152 "An error occurred while loading python module \"{}\": {}", _moduleName, python_error_as_string());
153
154 // Construct method performAction
155 _performAction = PyObject_GetAttrString(_module, "performAction");
156 if (PyErr_Occurred()) {
157 PyErr_Clear();
158 PRECICE_WARN("Python module \"{}\" does not define function performAction().", _moduleName);
159 _performAction = nullptr;
160 }
161}
162
164{
165 static bool importedAlready = false;
166 if (importedAlready)
167 return 0;
168 import_array1(-1); // this macro is defined be NumPy and must be included
169 importedAlready = true;
170 return 1;
171}
172
173} // namespace precice::action
174
175#endif
#define PRECICE_WARN(...)
Definition LogMacros.hpp:11
#define PRECICE_TRACE(...)
Definition LogMacros.hpp:95
#define PRECICE_CHECK(check,...)
Definition LogMacros.hpp:35
_object PyObject
#define PRECICE_ASSERT(...)
Definition assertion.hpp:87
T c_str(T... args)
Abstract base class for configurable actions on data and/or meshes.
Definition Action.hpp:14
Timing
Defines the time and place of application of the action.
Definition Action.hpp:17
const mesh::PtrMesh & getMesh() const
Returns the mesh carrying the data used in the action.
Definition Action.hpp:57
PythonAction(Timing timing, std::string modulePath, std::string moduleName, const mesh::PtrMesh &mesh, int targetDataID, int sourceDataID)
virtual void performAction() final override
Performs the action, to be overwritten by subclasses.
T is_directory(T... args)
contains actions to modify exchanged data.
Definition Action.hpp:6
constexpr bool equals(const Eigen::MatrixBase< DerivedA > &A, const Eigen::MatrixBase< DerivedB > &B, double tolerance=NUMERICAL_ZERO_DIFFERENCE)
Compares two Eigen::MatrixBase for equality up to tolerance.
std::string truncate_wstring_to_string(std::wstring wstr, char fill)
Definition String.cpp:65
STL namespace.