Возврат списка или кортежа массивов из pybind11, оборачивающего собственный

У меня есть функция C ++, использующая eigen, которая обернута с помощью pybind11, чтобы я мог вызывать ее из python. Простая версия предполагаемой функции возвращает тип Eigen::MatrixXd, который pybind успешно преобразует в двумерный массив numpy.

Я хотел бы, чтобы эта функция могла возвращать либо список, либо кортеж таких матриц, либо трехмерный массив numpy.

Я в некоторой степени новичок в C ++, и документация по pybind не дает (насколько я понимаю) каких-либо указаний. Ниже приведен фиктивный пример:

test.cpp

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <Eigen/Dense>

Eigen::MatrixXd test(Eigen::Ref<const Eigen::MatrixXd> x, double a)
{
  Eigen::MatrixXd y;
  y = x * a;
  return y;
}

Eigen::MatrixXd *test2(Eigen::Ref<const Eigen::MatrixXd> x, Eigen::Ref<const Eigen::VectorXd> as)
{
  Eigen::MatrixXd *ys = new Eigen::MatrixXd[as.size()];
  for(unsigned int k = 0; k < as.size(); k++){
    Eigen::MatrixXd& y = ys[k];
    y = x * as[k];
  }
  return ys;
}

namespace py = pybind11;

PYBIND11_MODULE(test, m)
{
  m.doc() = "minimal working example";
  m.def("test", &test);
  m.def("test2", &test2);
}

Я хочу, чтобы test2 возвращал список или кортеж массивов.

В питоне:

import test
import numpy as np
x = np.random.random((50, 50))
x = np.asfortranarray(x)
a = 0.1
a2 = np.array([1.0, 2.0, 3.0])
y = test.test(x, a)
ys = test.test2(x, a2)

Массив y соответствует ожиданиям, но ys содержит только массив, соответствующий первому коэффициенту a2.

Как мне изменить test2, чтобы правильно возвращать более одного массива? Также приемлем трехмерный массив.


person rxFt20    schedule 24.05.2019    source источник
comment
рассмотрите возможность рефакторинга test2, чтобы избежать необработанных указателей Основные принципы C ++   -  person Sergei    schedule 26.05.2019
comment
В этом конкретном случае я бы предпочел возврат по значению, как в test   -  person Sergei    schedule 26.05.2019


Ответы (2)


Я использовал Eigen раньше, но я не эксперт, поэтому другие могут улучшить это решение.

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <pybind11/stl.h>
#include <Eigen/Dense>

std::vector<Eigen::MatrixXd> 
test2(Eigen::Ref<const Eigen::MatrixXd> x, Eigen::Ref<const Eigen::VectorXd> as){
    std::vector<Eigen::MatrixXd> matrices;
    for(unsigned int k = 0; k < as.size(); k++){
        Eigen::MatrixXd ys = x * as[k];
        matrices.push_back(ys);
    }
    return matrices;
}

namespace py = pybind11;

PYBIND11_MODULE(test, m){
    m.doc() = "minimal working example";
    m.def("test2", &test2);
}

Вектор преобразуется в список массивов numpy с помощью pybind11. Результаты:

In [1]: import numpy as np; x = np.ones((2,2)); a = np.array((2., 3.)); import test

In [2]: test.test2(x, a)
Out[2]: 
[array([[2., 2.],
        [2., 2.]]), array([[3., 3.],
        [3., 3.]])]
person Olaf    schedule 25.05.2019

Я бы предложил вернуть std::tuple, но перемещая локальные объекты:

std::tuple<Eigen::MatrixXd,int> function(){
    ...
    int m = 4;
    Eigen::MatrixXd M = ...;
    ...

    return make_tuple(std::move(M),m);
}

В PYBIND11_MODULE я не совсем уверен, что правильно:

m.def("function", &function, py::return_value_policy::reference_internal);

or

m.def("function", &function);

Я протестировал обе работы по мере необходимости, то есть без копирования и выделения дополнительной памяти при возврате.

person Ashot Matevosyan    schedule 09.09.2020