preCICE v3.2.0
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 if (!_sourceData) {
99 PyObject *dataArgs = PyTuple_New(_numberArguments);
100 for (auto &targetStample : _targetData->stamples()) {
101 PyObject *pythonTime = PyFloat_FromDouble(targetStample.timestamp);
102 PyTuple_SetItem(dataArgs, 0, pythonTime);
103
104 npy_intp targetDim[] = {targetStample.sample.values.size()};
105 double *targetValues = const_cast<double *>(targetStample.sample.values.data());
106 _targetValues = PyArray_SimpleNewFromData(1, targetDim, NPY_DOUBLE, targetValues);
107 PRECICE_CHECK(_targetValues != nullptr, "Creating python target values failed. Please check that the target data name is used by the mesh in action:python.");
108 PyTuple_SetItem(dataArgs, 1, _targetValues);
109
110 PyObject_CallObject(_performAction, dataArgs);
111 PRECICE_CHECK(!PyErr_Occurred(),
112 "Error occurred during call of function performAction() in python module \"{}\". "
113 "The error message is: {}",
114 _moduleName, python_error_as_string());
115 }
116
117 _targetData->setGlobalSample(_targetData->stamples().back().sample);
118 Py_DECREF(dataArgs);
119 return;
120 }
121
122 PyObject *dataArgs = PyTuple_New(_numberArguments);
123 auto targetStamples = _targetData->stamples();
124 auto sourceStamples = _sourceData->stamples();
125 PRECICE_CHECK(targetStamples.size() == sourceStamples.size(), "Cannot perform python actions on different amount of samples in target and source data.");
126
127 for (size_t i = 0; i < targetStamples.size(); ++i) {
128 auto &targetStample = targetStamples[i];
129 auto &sourceStample = sourceStamples[i];
130 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);
131
132 PyObject *pythonTime = PyFloat_FromDouble(targetStample.timestamp);
133 PyTuple_SetItem(dataArgs, 0, pythonTime);
134
135 npy_intp sourceDim[] = {sourceStample.sample.values.size()};
136 double *sourceValues = const_cast<double *>(sourceStample.sample.values.data());
137 _sourceValues = PyArray_SimpleNewFromData(1, sourceDim, NPY_DOUBLE, sourceValues);
138 PRECICE_CHECK(_sourceValues != nullptr, "Creating python source values failed. Please check that the source data name is used by the mesh in action:python.");
139 PyTuple_SetItem(dataArgs, 1, _sourceValues);
140
141 npy_intp targetDim[] = {targetStample.sample.values.size()};
142 double *targetValues = const_cast<double *>(targetStample.sample.values.data());
143 _targetValues = PyArray_SimpleNewFromData(1, targetDim, NPY_DOUBLE, targetValues);
144 PRECICE_CHECK(_targetValues != nullptr, "Creating python target values failed. Please check that the target data name is used by the mesh in action:python.");
145 PyTuple_SetItem(dataArgs, 2, _targetValues);
146
147 PyObject_CallObject(_performAction, dataArgs);
148 PRECICE_CHECK(!PyErr_Occurred(),
149 "Error occurred during call of function performAction() in python module \"{}\". "
150 "The error message is: {}",
151 _moduleName, python_error_as_string());
152 }
153 _targetData->setGlobalSample(_targetData->stamples().back().sample);
154 Py_DECREF(dataArgs);
155}
156
158{
160 // Initialize Python
161 Py_Initialize();
163 // Append execution path to find module to import
164 PyRun_SimpleString("import sys");
165 std::string appendPathCommand("sys.path.append('" + _modulePath + "')");
166 PyRun_SimpleString(appendPathCommand.c_str());
167 _moduleNameObject = PyUnicode_FromString(_moduleName.c_str());
168 _module = PyImport_Import(_moduleNameObject);
170 "An error occurred while loading python module \"{}\": {}", _moduleName, python_error_as_string());
171
172 // Construct method performAction
173 _performAction = PyObject_GetAttrString(_module, "performAction");
174 if (PyErr_Occurred()) {
175 PyErr_Clear();
176 PRECICE_WARN("Python module \"{}\" does not define function performAction().", _moduleName);
177 _performAction = nullptr;
178 }
179}
180
182{
183 static bool importedAlready = false;
184 if (importedAlready)
185 return 0;
186 import_array1(-1); // this macro is defined be NumPy and must be included
187 importedAlready = true;
188 return 1;
189}
190
191} // namespace precice::action
192
193#endif
#define PRECICE_WARN(...)
Definition LogMacros.hpp:12
#define PRECICE_TRACE(...)
Definition LogMacros.hpp:92
#define PRECICE_CHECK(check,...)
Definition LogMacros.hpp:32
_object PyObject
#define PRECICE_ASSERT(...)
Definition assertion.hpp:85
T c_str(T... args)
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
Action(Timing timing, const mesh::PtrMesh &mesh, mapping::Mapping::MeshRequirement requirement)
Definition Action.hpp:22
PythonAction(Timing timing, std::string modulePath, std::string moduleName, const mesh::PtrMesh &mesh, int targetDataID, int sourceDataID)
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.
provides Mesh, Data and primitives.
std::shared_ptr< Mesh > PtrMesh
std::string truncate_wstring_to_string(std::wstring wstr, char fill)
Definition String.cpp:66
STL namespace.