ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
PyArrayAccessor.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include "PyArrayAccessor.h"
5
6#include <pybind11/stl.h>
7
8namespace py = pybind11;
9
10namespace ChimeraTK {
11
12 /********************************************************************************************************************/
13
14 template<template<typename> class AccessorType>
15 UserTypeTemplateVariantNoVoid<ArrayAccessor> PyArrayAccessor::createAccessor(ChimeraTK::DataType type, Module* owner,
16 const std::string& name, std::string unit, size_t nElements, const std::string& description,
17 const std::unordered_set<std::string>& tags) {
18 std::optional<UserTypeTemplateVariantNoVoid<ArrayAccessor>> rv;
19 ChimeraTK::callForTypeNoVoid(type, [&](auto t) {
20 using UserType = decltype(t);
21 AccessorType<UserType> acc(owner, name, unit, nElements, description, tags);
22 rv.emplace(std::in_place_type<ArrayAccessor<UserType>>, std::move(acc));
23 });
24 return std::move(rv.value());
25 }
26
27 /********************************************************************************************************************/
28
30 read();
31 return get();
32 }
33
34 /********************************************************************************************************************/
35
36 void PyArrayAccessor::setAndWrite(const UserTypeTemplateVariantNoVoid<Vector>& vec) {
37 set(vec);
38 write();
39 }
40 /********************************************************************************************************************/
41
43 size_t rv;
44 std::visit([&](auto& acc) { rv = acc.getNElements(); }, _accessor);
45 return rv;
46 }
47 /********************************************************************************************************************/
48
49 void PyArrayAccessor::set(const UserTypeTemplateVariantNoVoid<Vector>& vec) {
50 std::visit(
51 [&](auto& acc) {
52 using ACC = typename std::remove_reference<decltype(acc)>::type;
53 using expectedUserType = typename ACC::value_type;
54 std::visit(
55 [&](const auto& vector) {
56 std::vector<expectedUserType> converted(vector.size());
57 std::transform(vector.begin(), vector.end(), converted.begin(),
58 [](auto v) { return userTypeToUserType<expectedUserType>(v); });
59 acc = converted;
60 },
61 vec);
62 },
63 _accessor);
64 }
65 /********************************************************************************************************************/
66
67 py::object PyArrayAccessor::get() const {
68 py::object rv;
69 std::visit(
70 [&](auto& acc) {
71 using ACC = typename std::remove_reference<decltype(acc)>::type;
72 using userType = typename ACC::value_type;
73 auto ndacc = boost::dynamic_pointer_cast<NDRegisterAccessor<userType>>(acc.getHighLevelImplElement());
74 if constexpr(std::is_same<userType, std::string>::value) {
75 // String arrays are not really supported by numpy, so we return a list instead
76 rv = py::cast(ndacc->accessChannel(0));
77 }
78 else {
79 auto ary = py::array(py::dtype::of<userType>(), {acc.getNElements()}, {sizeof(userType)},
80 ndacc->accessChannel(0).data(), py::cast(this));
81 assert(!ary.owndata()); // numpy must not own our buffers
82 rv = ary;
83 }
84 },
85 _accessor);
86 return rv;
87 }
88 /********************************************************************************************************************/
89
90 py::object PyArrayAccessor::getitem(size_t index) const {
91 py::object rv;
92 std::visit([&](auto& acc) { rv = py::cast(acc[index]); }, _accessor);
93 return rv;
94 }
95
96 /********************************************************************************************************************/
97
98 void PyArrayAccessor::setitem(size_t index, const UserTypeVariantNoVoid& val) {
99 std::visit(
100 [&](auto& acc) {
101 std::visit(
102 [&](auto& v) {
103 acc[index] = userTypeToUserType<typename std::remove_reference<decltype(acc)>::type::value_type>(v);
104 },
105 val);
106 },
107 _accessor);
108 }
109
110 /********************************************************************************************************************/
111
112 void PyArrayAccessor::setslice(const py::slice& slice, const UserTypeVariantNoVoid& val) {
113 std::visit(
114 [&](auto& acc) {
115 std::visit(
116 [&](auto& v) {
117 size_t start, stop, step, length;
118 if(!slice.compute(acc.getNElements(), &start, &stop, &step, &length)) {
119 throw pybind11::error_already_set();
120 }
121
122 auto value = userTypeToUserType<typename std::remove_reference<decltype(acc)>::type::value_type>(v);
123 for(size_t i = start; i < stop; i += step) {
124 acc[i] = value;
125 }
126 },
127 val);
128 },
129 _accessor);
130 }
131
132 /********************************************************************************************************************/
133
134 std::string PyArrayAccessor::repr(py::object& acc) {
135 if(not acc.cast<PyArrayAccessor&>().getTE().isInitialised()) {
136 return "<ArrayAccessor(not initialized)>";
137 }
138
139 std::string rep{"<ArrayAccessor(type="};
140 rep.append(py::cast<py::object>(py::cast(&acc).attr("getValueType")()).attr("__repr__")().cast<std::string>());
141 rep.append(", name=");
142 rep.append(py::cast(&acc).attr("getName")().cast<std::string>());
143 rep.append(", data=");
144 rep.append(py::cast<py::object>(py::cast(&acc).attr("__str__")()).cast<std::string>());
145 rep.append(", versionNumber=");
146 rep.append(py::cast<py::object>(py::cast(&acc).attr("getVersionNumber")()).attr("__repr__")().cast<std::string>());
147 rep.append(", dataValidity=");
148 rep.append(py::cast<py::object>(py::cast(&acc).attr("dataValidity")()).attr("__repr__")().cast<std::string>());
149 rep.append(")>");
150 return rep;
151 }
152 /********************************************************************************************************************/
153
155 py::buffer_info info;
156 std::visit(
157 [&](auto& acc) {
158 using ACC = typename std::remove_reference<decltype(acc)>::type;
159 using userType = typename ACC::value_type;
160 auto ndacc = boost::dynamic_pointer_cast<NDRegisterAccessor<userType>>(acc.getHighLevelImplElement());
161 if constexpr(std::is_same<userType, ChimeraTK::Boolean>::value) {
162 info.format = py::format_descriptor<bool>::format();
163 }
164 else if constexpr(std::is_same<userType, std::string>::value) {
165 // cannot implement
166 return;
167 }
168 else {
169 info.format = py::format_descriptor<userType>::format();
170 }
171 info.ptr = ndacc->accessChannel(0).data();
172 info.itemsize = sizeof(userType);
173 info.ndim = 1;
174 info.shape = {acc.getNElements()};
175 info.strides = {sizeof(userType)};
176 },
177 _accessor);
178 return info;
179 }
180
181 /********************************************************************************************************************/
182
184
185 /********************************************************************************************************************/
186
188 py::class_<PyArrayAccessor, PyTransferElementBase, std::unique_ptr<PyArrayAccessor, py::nodelete>> arrayacc(
189 m, "ArrayAccessor", py::buffer_protocol());
190 arrayacc.def(py::init<>())
192 .def("read", &PyArrayAccessor::read,
193 "Read the data from the device.\n\nIf AccessMode::wait_for_new_data was set, this function will block "
194 "until new data has arrived. Otherwise it still might block for a short time until the data transfer was "
195 "complete.")
196 .def("readNonBlocking", &PyArrayAccessor::readNonBlocking,
197 "Read the next value, if available in the input buffer.\n\nIf AccessMode::wait_for_new_data was set, this "
198 "function returns immediately and the return value indicated if a new value was available (true) or not "
199 "(false).\n\nIf AccessMode::wait_for_new_data was not set, this function is identical to read(), which "
200 "will still return quickly. Depending on the actual transfer implementation, the backend might need to "
201 "transfer data to obtain the current value before returning. Also this function is not guaranteed to be "
202 "lock free. The return value will be always true in this mode.")
203 .def("readLatest", &PyArrayAccessor::readLatest,
204 "Read the latest value, discarding any other update since the last read if present.\n\nOtherwise this "
205 "function is identical to readNonBlocking(), i.e. it will never wait for new values and it will return "
206 "whether a new value was available if AccessMode::wait_for_new_data is set.")
207 .def("write", &PyArrayAccessor::write,
208 "Write the data to device.\n\nThe return value is true, old data was lost on the write transfer (e.g. due "
209 "to an buffer overflow). In case of an unbuffered write transfer, the return value will always be false.")
210 .def("writeDestructively", &PyArrayAccessor::writeDestructively,
211 "Just like write(), but allows the implementation to destroy the content of the user buffer in the "
212 "process.\n\nThis is an optional optimisation, hence there is a default implementation which just calls "
213 "the normal doWriteTransfer(). In any case, the application must expect the user buffer of the "
214 "TransferElement to contain undefined data after calling this function.")
215 .def("getName", &PyArrayAccessor::getName, "Returns the name that identifies the process variable.")
216 .def("getUnit", &PyArrayAccessor::getUnit,
217 "Returns the engineering unit.\n\nIf none was specified, it will default to ' n./ a.'")
218 .def("getDescription", &PyArrayAccessor::getDescription, "Returns the description of this variable/register.")
219 .def("getValueType", &PyArrayAccessor::getValueType,
220 "Returns the std::type_info for the value type of this transfer element.\n\nThis can be used to determine "
221 "the type at runtime.")
222 .def("getVersionNumber", &PyArrayAccessor::getVersionNumber,
223 "Returns the version number that is associated with the last transfer (i.e. last read or write)")
224 .def("isReadOnly", &PyArrayAccessor::isReadOnly,
225 "Check if transfer element is read only, i.e. it is readable but not writeable.")
226 .def("isReadable", &PyArrayAccessor::isReadable, "Check if transfer element is readable.")
227 .def("isWriteable", &PyArrayAccessor::isWriteable, "Check if transfer element is writeable.")
228 .def("getId", &PyArrayAccessor::getId,
229 "Obtain unique ID for the actual implementation of this TransferElement.\n\nThis means that e.g. two "
230 "instances of ScalarRegisterAccessor created by the same call to Device::getScalarRegisterAccessor() (e.g. "
231 "by copying the accessor to another using NDRegisterAccessorBridge::replace()) will have the same ID, "
232 "while two instances obtained by to difference calls to Device::getScalarRegisterAccessor() will have a "
233 "different ID even when accessing the very same register.")
234 .def("dataValidity", &PyArrayAccessor::dataValidity,
235 "Return current validity of the data.\n\nWill always return DataValidity.ok if the backend does not "
236 "support it")
237 .def("getNElements", &PyArrayAccessor::getNElements, "Return number of elements/samples in the register.")
238 .def("get", &PyArrayAccessor::get, "Return an array of UserType (without a previous read).")
239 .def("set", &PyArrayAccessor::set, "Set the values of the array of UserType.", py::arg("newValue"))
240 .def("setAndWrite", &PyArrayAccessor::setAndWrite,
241 "Convenience function to set and write new value.\n\nThe given version number. If versionNumber == {}, a "
242 "new version number is generated.",
243 py::arg("newValue"))
244 .def(
245 "readAndGet", &PyArrayAccessor::readAndGet, "Convenience function to read and return an array of UserType.")
246 .def("__repr__", &PyArrayAccessor::repr)
247 .def("__getitem__", &PyArrayAccessor::getitem)
248 .def("__setitem__", &PyArrayAccessor::setitem)
249 .def("__setitem__", &PyArrayAccessor::setslice)
250 .def("__getattr__", &PyArrayAccessor::getattr);
252 arrayacc.def(fn.c_str(),
253 [fn](PyArrayAccessor& acc, PyArrayAccessor& other) { return acc.get().attr(fn.c_str())(other.get()); });
254 arrayacc.def(
255 fn.c_str(), [fn](PyArrayAccessor& acc, py::object& other) { return acc.get().attr(fn.c_str())(other); });
256 }
258 arrayacc.def(fn.c_str(), [fn](PyArrayAccessor& acc) { return acc.get().attr(fn.c_str())(); });
259 }
260
264 m.def(
265 "ArrayPushInput",
266 [](ChimeraTK::DataType type, VariableGroup& owner, const std::string& name, const std::string& unit,
267 size_t nElements, const std::string& description) {
268 return dynamic_cast<PyOwningObject&>(owner).make_child<PyArrayAccessor>(
269 AccessorTypeTag<ArrayPushInput>{}, type, &owner, name, unit, nElements, description);
270 },
271 py::return_value_policy::reference, "");
272
276 m.def(
277 "ArrayPushInputWB",
278 [](ChimeraTK::DataType type, VariableGroup& owner, const std::string& name, const std::string& unit,
279 size_t nElements, const std::string& description) {
280 return dynamic_cast<PyOwningObject&>(owner).make_child<PyArrayAccessor>(
281 AccessorTypeTag<ArrayPushInputWB>{}, type, &owner, name, unit, nElements, description);
282 },
283 py::return_value_policy::reference, "");
284
288 m.def(
289 "ArrayPollInput",
290 [](ChimeraTK::DataType type, VariableGroup& owner, const std::string& name, const std::string& unit,
291 size_t nElements, const std::string& description) {
292 return dynamic_cast<PyOwningObject&>(owner).make_child<PyArrayAccessor>(
293 AccessorTypeTag<ArrayPollInput>{}, type, &owner, name, unit, nElements, description);
294 },
295 py::return_value_policy::reference, "");
296
300 m.def(
301 "ArrayOutput",
302 [](ChimeraTK::DataType type, VariableGroup& owner, const std::string& name, const std::string& unit,
303 size_t nElements, const std::string& description) {
304 return dynamic_cast<PyOwningObject&>(owner).make_child<PyArrayAccessor>(
305 AccessorTypeTag<ArrayOutput>{}, type, &owner, name, unit, nElements, description);
306 },
307 py::return_value_policy::reference, "");
308
312 m.def(
313 "ArrayOutputPushRB",
314 [](ChimeraTK::DataType type, VariableGroup& owner, const std::string& name, const std::string& unit,
315 size_t nElements, const std::string& description) {
316 return dynamic_cast<PyOwningObject&>(owner).make_child<PyArrayAccessor>(
317 AccessorTypeTag<ArrayOutputPushRB>{}, type, &owner, name, unit, nElements, description);
318 },
319 py::return_value_policy::reference, "");
320
324 m.def(
325 "ArrayOutputReverseRecovery",
326 [](ChimeraTK::DataType type, VariableGroup& owner, const std::string& name, const std::string& unit,
327 size_t nElements, const std::string& description) {
328 return dynamic_cast<PyOwningObject&>(owner).make_child<PyArrayAccessor>(
329 AccessorTypeTag<ArrayOutputReverseRecovery>{}, type, &owner, name, unit, nElements, description);
330 },
331 py::return_value_policy::reference, "");
332 }
333
334 /********************************************************************************************************************/
335
336} // namespace ChimeraTK
Helper class acting as a ArrayAccessor with a variant UserType.
py::object getitem(size_t index) const
void set(const UserTypeTemplateVariantNoVoid< Vector > &vec)
static void bind(py::module &mod)
void setAndWrite(const UserTypeTemplateVariantNoVoid< Vector > &vec)
void setslice(const py::slice &slice, const UserTypeVariantNoVoid &val)
static std::string repr(py::object &acc)
void setitem(size_t index, const UserTypeVariantNoVoid &val)
UserTypeTemplateVariantNoVoid< ArrayAccessor > _accessor
py::buffer_info getBufferInfo()
py::object getattr(const std::string &name) const
Base class used for all objects in the Python world which can own other objects and can be owned them...
static const std::set< std::string > specialUnaryFunctionsToEmulateNumeric
static const std::set< std::string > specialFunctionsToEmulateNumeric
const TransferElementAbstractor & getTE() const final
InvalidityTracer application module.
module_ module