LCOV - code coverage report
Current view: top level - python - bind_data_array.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 199 215 92.6 %
Date: 2024-04-28 01:25:40 Functions: 193 244 79.1 %

          Line data    Source code
       1             : // SPDX-License-Identifier: BSD-3-Clause
       2             : // Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
       3             : /// @file
       4             : /// @author Simon Heybrock
       5             : #pragma once
       6             : 
       7             : #include "scipp/dataset/dataset.h"
       8             : #include "scipp/variable/variable_factory.h"
       9             : 
      10             : #include "bind_operators.h"
      11             : #include "pybind11.h"
      12             : #include "view.h"
      13             : 
      14             : namespace py = pybind11;
      15             : using namespace scipp;
      16             : 
      17             : template <template <class> class View, class T>
      18          45 : void bind_helper_view(py::module &m, const std::string &name) {
      19          45 :   std::string suffix;
      20             :   if constexpr (std::is_same_v<View<T>, items_view<T>> ||
      21             :                 std::is_same_v<View<T>, str_items_view<T>>)
      22          15 :     suffix = "_items_view";
      23             :   if constexpr (std::is_same_v<View<T>, values_view<T>>)
      24          15 :     suffix = "_values_view";
      25             :   if constexpr (std::is_same_v<View<T>, keys_view<T>> ||
      26             :                 std::is_same_v<View<T>, str_keys_view<T>>)
      27          15 :     suffix = "_keys_view";
      28          45 :   auto cls =
      29          90 :       py::class_<View<T>>(m, (name + suffix).c_str())
      30          45 :           .def("__len__", &View<T>::size)
      31         267 :           .def("__repr__", [](const View<T> &self) { return self.tostring(); })
      32          75 :           .def("__str__", [](const View<T> &self) { return self.tostring(); })
      33          45 :           .def(
      34             :               "__iter__",
      35       14630 :               [](const View<T> &self) {
      36             :                 return py::make_iterator(self.begin(), self.end(),
      37       14630 :                                          py::return_value_policy::move);
      38             :               },
      39          90 :               py::return_value_policy::move, py::keep_alive<0, 1>());
      40             :   if constexpr (!std::is_same_v<View<T>, values_view<T>>)
      41        1423 :     cls.def("__eq__", [](const View<T> &self, const View<T> &other) {
      42        1393 :       return self == other;
      43             :     });
      44          45 : }
      45             : 
      46             : template <class T, class... Ignored>
      47          18 : void bind_common_mutable_view_operators(pybind11::class_<T, Ignored...> &view) {
      48          18 :   view.def("__len__", &T::size)
      49           0 :       .def(
      50             :           "__getitem__",
      51       39332 :           [](const T &self, const std::string &key) {
      52       73056 :             return self[typename T::key_type{key}];
      53             :           },
      54          18 :           py::return_value_policy::copy)
      55          18 :       .def("__setitem__",
      56       27515 :            [](T &self, const std::string &key, const Variable &var) {
      57       13773 :              self.set(typename T::key_type{key}, var);
      58             :            })
      59          18 :       .def(
      60             :           "__delitem__",
      61        5088 :           [](T &self, const std::string &key) {
      62        2549 :             self.erase(typename T::key_type{key});
      63             :           },
      64           0 :           py::call_guard<py::gil_scoped_release>())
      65       15603 :       .def("__contains__", [](const T &self, const std::string &key) {
      66       15585 :         return self.contains(typename T::key_type{key});
      67             :       });
      68          18 : }
      69             : 
      70          55 : template <class D> auto cast_to_dict_key(const py::handle &obj) {
      71             :   using key_type = typename D::key_type;
      72             :   if constexpr (std::is_same_v<key_type, std::string>) {
      73          23 :     return obj.cast<std::string>();
      74             :   } else {
      75          32 :     return key_type{obj.cast<std::string>()};
      76             :   }
      77             : }
      78             : 
      79          55 : template <class D> auto cast_to_dict_value(const py::handle &obj) {
      80             :   using val_type = typename D::mapped_type;
      81          55 :   return obj.cast<val_type>();
      82             : }
      83             : 
      84             : template <class T, class... Ignored, class Set>
      85          15 : void bind_dict_update(pybind11::class_<T, Ignored...> &view, Set &&set_item) {
      86          15 :   view.def(
      87             :       "update",
      88          38 :       [set_item](T &self, const py::object &other, const py::kwargs &kwargs) {
      89             :         // Piggyback on dict to implement argument handling.
      90          38 :         py::dict args;
      91          38 :         if (!other.is_none()) {
      92          29 :           args.attr("update")(other, **kwargs);
      93             :         } else {
      94             :           // Cannot call dict.update(None, **kwargs) because dict.update
      95             :           // expects either an iterable as the positional argument or nothing
      96             :           // at all (nullptr). But we cannot express 'nothing' here.
      97             :           // The best we can do it pass None, which does not work.
      98           9 :           args.attr("update")(**kwargs);
      99             :         }
     100             : 
     101          93 :         for (const auto &[key, val] : args) {
     102          55 :           set_item(self, cast_to_dict_key<T>(key), cast_to_dict_value<T>(val));
     103             :         }
     104          38 :       },
     105          30 :       py::arg("other") = py::none(), py::pos_only(),
     106             :       R"doc(Update items from dict-like or iterable.
     107             : 
     108             : If ``other`` has a .keys() method, then update does:
     109             : ``for k in other.keys(): self[k] = other[k]``.
     110             : 
     111             : If ``other`` is given but does not have a .keys() method, then update does:
     112             : ``for k, v in other: self[k] = v``.
     113             : 
     114             : In either case, this is followed by:
     115             : ``for k in kwargs: self[k] = kwargs[k]``.
     116             : 
     117             : See Also
     118             : --------
     119             : dict.update
     120             : )doc");
     121          15 : }
     122             : 
     123             : template <class T, class... Ignored>
     124          21 : void bind_pop(pybind11::class_<T, Ignored...> &view) {
     125          21 :   view.def(
     126             :       "_pop",
     127         201 :       [](T &self, const std::string &key) {
     128         201 :         return py::cast(self.extract(typename T::key_type{key}));
     129             :       },
     130          21 :       py::arg("k"));
     131          21 : }
     132             : 
     133             : template <class T, class... Ignored>
     134           6 : void bind_set_aligned(pybind11::class_<T, Ignored...> &view) {
     135           6 :   view.def(
     136             :       "set_aligned",
     137             :       // cppcheck-suppress constParameter  # False positive.
     138        1168 :       [](T &self, const std::string &key, const bool aligned) {
     139         584 :         self.set_aligned(typename T::key_type{key}, aligned);
     140             :       },
     141           0 :       py::arg("key"), py::arg("aligned"));
     142           6 : }
     143             : 
     144             : template <class T, class... Ignored>
     145          12 : void bind_dict_clear(pybind11::class_<T, Ignored...> &view) {
     146          19 :   view.def("clear", [](T &self) {
     147           7 :     std::vector<typename T::key_type> keys;
     148          19 :     for (const auto &key : keys_view(self))
     149          12 :       keys.push_back(key);
     150          19 :     for (const auto &key : keys)
     151          12 :       self.erase(key);
     152           7 :   });
     153          12 : }
     154             : 
     155             : template <class T, class... Ignored>
     156          12 : void bind_dict_popitem(pybind11::class_<T, Ignored...> &view) {
     157          20 :   view.def("popitem", [](T &self) {
     158           2 :     typename T::key_type key;
     159          20 :     for (const auto &k : keys_view(self))
     160          12 :       key = k;
     161           8 :     const auto item = py::cast(self.extract(key));
     162             :     if constexpr (std::is_same_v<typename T::key_type, Dim>)
     163          12 :       return std::tuple{key.name(), item};
     164             :     else
     165           4 :       return std::tuple{key, item};
     166           8 :   });
     167          12 : }
     168             : 
     169             : template <class T, class... Ignored>
     170          12 : void bind_dict_copy(pybind11::class_<T, Ignored...> &view) {
     171          12 :   view.def(
     172             :           "copy",
     173           8 :           [](const T &self, const bool deep) {
     174           8 :             return deep ? copy(self) : self;
     175             :           },
     176          12 :           py::arg("deep") = true, py::call_guard<py::gil_scoped_release>(),
     177             :           R"(
     178             :       Return a (by default deep) copy.
     179             : 
     180             :       If `deep=True` (the default), a deep copy is made. Otherwise, a shallow
     181             :       copy is made, and the returned data (and meta data) values are new views
     182             :       of the data and meta data values of this object.)")
     183          12 :       .def(
     184           0 :           "__copy__", [](const T &self) { return self; },
     185           0 :           py::call_guard<py::gil_scoped_release>(), "Return a (shallow) copy.")
     186          12 :       .def(
     187             :           "__deepcopy__",
     188           0 :           [](const T &self, const py::dict &) { return copy(self); },
     189          12 :           py::call_guard<py::gil_scoped_release>(), "Return a (deep) copy.");
     190          12 : }
     191             : 
     192             : template <class T, class... Ignored>
     193          12 : void bind_is_edges(py::class_<T, Ignored...> &view) {
     194          12 :   view.def(
     195             :       "is_edges",
     196        4801 :       [](const T &self, const std::string &key,
     197             :          const std::optional<std::string> &dim) {
     198        9073 :         return self.is_edges(typename T::key_type{key},
     199       13873 :                              dim.has_value() ? std::optional{Dim(*dim)}
     200        9602 :                                              : std::optional<Dim>{});
     201             :       },
     202          24 :       py::arg("key"), py::arg("dim") = std::nullopt,
     203             :       R"(Return True if the given key contains bin-edges in the given dim.)");
     204          12 : }
     205             : 
     206             : template <class T>
     207           6 : void bind_mutable_view(py::module &m, const std::string &name,
     208             :                        const std::string &docs) {
     209           6 :   py::class_<T> view(m, name.c_str(), docs.c_str());
     210           6 :   bind_common_mutable_view_operators(view);
     211           6 :   bind_inequality_to_operator<T>(view);
     212           6 :   bind_dict_update(view, [](T &self, const std::string &key,
     213          12 :                             const Variable &value) { self.set(key, value); });
     214           6 :   bind_pop(view);
     215           6 :   bind_dict_clear(view);
     216           6 :   bind_dict_popitem(view);
     217           6 :   bind_dict_copy(view);
     218           6 :   bind_is_edges(view);
     219           6 :   view.def(
     220             :           "__iter__",
     221         298 :           [](const T &self) {
     222             :             return py::make_iterator(self.keys_begin(), self.keys_end(),
     223         298 :                                      py::return_value_policy::move);
     224             :           },
     225           0 :           py::keep_alive<0, 1>())
     226           6 :       .def(
     227        1090 :           "keys", [](T &self) { return keys_view(self); },
     228           0 :           py::keep_alive<0, 1>(), R"(view on self's keys)")
     229           6 :       .def(
     230        3246 :           "values", [](T &self) { return values_view(self); },
     231           0 :           py::keep_alive<0, 1>(), R"(view on self's values)")
     232           0 :       .def(
     233        3114 :           "items", [](T &self) { return items_view(self); },
     234           6 :           py::return_value_policy::move, py::keep_alive<0, 1>(),
     235             :           R"(view on self's items)")
     236           6 :       .def("_ipython_key_completions_",
     237           1 :            [](const T &self) {
     238           1 :              py::list out;
     239           1 :              const auto end = self.keys_end();
     240           2 :              for (auto it = self.keys_begin(); it != end; ++it) {
     241           1 :                out.append(*it);
     242             :              }
     243           2 :              return out;
     244           0 :            })
     245          40 :       .def("__repr__", [name](const T &self) { return to_string(self); })
     246           8 :       .def("__str__", [name](const T &self) { return to_string(self); });
     247           6 : }
     248             : 
     249             : template <class T>
     250           6 : void bind_mutable_view_no_dim(py::module &m, const std::string &name,
     251             :                               const std::string &docs) {
     252           6 :   py::class_<T> view(m, name.c_str(), docs.c_str());
     253           6 :   bind_common_mutable_view_operators(view);
     254           6 :   bind_inequality_to_operator<T>(view);
     255           6 :   bind_dict_update(view, [](T &self, const units::Dim &key,
     256          32 :                             const Variable &value) { self.set(key, value); });
     257           6 :   bind_pop(view);
     258           6 :   bind_set_aligned(view);
     259           6 :   bind_dict_clear(view);
     260           6 :   bind_dict_popitem(view);
     261           6 :   bind_dict_copy(view);
     262           6 :   bind_is_edges(view);
     263           6 :   view.def(
     264             :           "__iter__",
     265         397 :           [](T &self) {
     266         397 :             auto keys_view = str_keys_view(self);
     267             :             return py::make_iterator(keys_view.begin(), keys_view.end(),
     268         397 :                                      py::return_value_policy::move);
     269             :           },
     270           0 :           py::keep_alive<0, 1>())
     271           6 :       .def(
     272        2927 :           "keys", [](T &self) { return str_keys_view(self); },
     273           0 :           py::keep_alive<0, 1>(), R"(view on self's keys)")
     274           6 :       .def(
     275          92 :           "values", [](T &self) { return values_view(self); },
     276           0 :           py::keep_alive<0, 1>(), R"(view on self's values)")
     277           0 :       .def(
     278        6271 :           "items", [](T &self) { return str_items_view(self); },
     279           6 :           py::return_value_policy::move, py::keep_alive<0, 1>(),
     280             :           R"(view on self's items)")
     281           6 :       .def("_ipython_key_completions_",
     282           3 :            [](const T &self) {
     283           3 :              py::list out;
     284           3 :              const auto end = self.keys_end();
     285          11 :              for (auto it = self.keys_begin(); it != end; ++it) {
     286           8 :                out.append(it->name());
     287             :              }
     288           6 :              return out;
     289           0 :            })
     290         101 :       .def("__repr__", [name](const T &self) { return to_string(self); })
     291          13 :       .def("__str__", [name](const T &self) { return to_string(self); });
     292           6 : }
     293             : 
     294             : template <class T, class... Ignored>
     295           6 : void bind_data_array_properties(py::class_<T, Ignored...> &c) {
     296             :   if constexpr (std::is_same_v<T, DataArray>)
     297           3 :     c.def_property("name", &T::name, &T::setName,
     298             :                    R"(The name of the held data.)");
     299             :   else
     300           3 :     c.def_property_readonly("name", &T::name, R"(The name of the held data.)");
     301           6 :   c.def_property(
     302             :       "data",
     303       28195 :       py::cpp_function([](T &self) { return self.data(); },
     304           6 :                        py::return_value_policy::copy),
     305         152 :       [](T &self, const Variable &data) { self.setData(data); },
     306             :       R"(Underlying data item.)");
     307           6 :   c.def_property_readonly(
     308       70259 :       "coords", [](T &self) -> decltype(auto) { return self.coords(); },
     309             :       R"(Dict of coords.)");
     310           6 :   c.def_property_readonly(
     311        7651 :       "deprecated_meta", [](T &self) -> decltype(auto) { return self.meta(); },
     312             :       R"(Dict of coords and attrs.)");
     313           6 :   c.def_property_readonly(
     314             :       "deprecated_attrs",
     315        4223 :       [](T &self) -> decltype(auto) { return self.attrs(); },
     316             :       R"(Dict of attrs.)");
     317           6 :   c.def_property_readonly(
     318        8907 :       "masks", [](T &self) -> decltype(auto) { return self.masks(); },
     319             :       R"(Dict of masks.)");
     320           6 :   c.def("drop_coords", [](T &self, const std::string &coord_name) {
     321           3 :     std::vector<scipp::Dim> coord_names_c = {scipp::Dim{coord_name}};
     322           6 :     return self.drop_coords(coord_names_c);
     323           3 :   });
     324           6 :   c.def("drop_coords",
     325           4 :         [](T &self, const std::vector<std::string> &coord_names) {
     326           4 :           std::vector<scipp::Dim> coord_names_c;
     327           4 :           std::transform(coord_names.begin(), coord_names.end(),
     328             :                          std::back_inserter(coord_names_c),
     329           7 :                          [](const auto &name) { return scipp::Dim{name}; });
     330           8 :           return self.drop_coords(coord_names_c);
     331           4 :         });
     332           6 :   c.def("drop_masks", [](T &self, const std::string &mask_name) {
     333           4 :     return self.drop_masks(std::vector({mask_name}));
     334             :   });
     335           6 :   c.def("drop_masks", [](T &self, std::vector<std::string> &mask_names) {
     336           4 :     return self.drop_masks(mask_names);
     337             :   });
     338           6 :   c.def("deprecated_drop_attrs", [](T &self, std::string &attr_name) {
     339           2 :     std::vector<scipp::Dim> attr_names_c = {scipp::Dim{attr_name}};
     340           4 :     return self.drop_attrs(attr_names_c);
     341           2 :   });
     342           6 :   c.def("deprecated_drop_attrs",
     343           4 :         [](T &self, std::vector<std::string> &attr_names) {
     344           4 :           std::vector<scipp::Dim> attr_names_c;
     345           4 :           std::transform(attr_names.begin(), attr_names.end(),
     346             :                          std::back_inserter(attr_names_c),
     347           7 :                          [](const auto &name) { return scipp::Dim{name}; });
     348           8 :           return self.drop_attrs(attr_names_c);
     349           4 :         });
     350           6 : }

Generated by: LCOV version 1.14