pybind11 icon indicating copy to clipboard operation
pybind11 copied to clipboard

Wrong overload picked for numpy 1D arrays

Open simleo opened this issue 5 years ago • 1 comments

Issue description

When an overloaded function has one version that takes a vector and another that takes a numpy array, the one that takes a vector can be called if a one-dimensional numpy array is passed as argument.

Reproducible example code

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <vector>
#include <iostream>

namespace py = pybind11;

class Tensor {
public:
    int size;
    std::vector<int> shape;
    float *data;
    Tensor(const std::vector<int>& shape) {
	this->shape = shape;
	size = 1;
	for (size_t i = 0; i < shape.size(); ++i) {
	    size *= shape[i];
	}
	data = new float[size];
    }
    void info() {
	std::cout << "shape: [ ";
	for (size_t i = 0; i < shape.size(); ++i) {
	    std::cout << shape[i] << " ";
	}
	std::cout << "]" << std::endl;
	std::cout << "data: [ ";
	for (int i = 0; i < size; ++i) {
	    std::cout << data[i] << " ";
	}
	std::cout << "]" << std::endl;
    }
};

Tensor* create(const std::vector<int>& shape) {
    return new Tensor(shape);
}

PYBIND11_MODULE(_ext, m) {
    py::class_<Tensor>(m, "Tensor")
	.def(py::init<const std::vector<int>&>())
	.def("info", &Tensor::info);
    m.def("create", (class Tensor* (*)(const std::vector<int>&)) &create,
	  "create(list) --> Tensor", pybind11::arg("shape"));
    m.def("create", [](py::array_t<float, py::array::c_style | py::array::forcecast> array) -> class Tensor* {
        py::buffer_info info = array.request();
        std::vector<int> shape(info.shape.begin(), info.shape.end());
        Tensor* t = new Tensor(shape);
        std::copy((float*)info.ptr, ((float*)info.ptr) + t->size, t->data);
        return t;
    }, "create(array) --> Tensor", py::arg("array"));
}

If a 1D array is passed to create, the vector-based version is picked instead of the array-based one:

import _ext
import numpy as np

t = _ext.create([2, 2])
t.info()
print()

a = np.arange(6).reshape(2, 3).astype(np.float32)
t = _ext.create(a)
t.info()
print()

b = np.array([1, 2]).astype(np.float32)
u = _ext.create(b)
u.info()

Output:

shape: [ 2 2 ]
data: [ 1.22025e+21 4.57678e-41 1.22025e+21 4.57678e-41 ]

shape: [ 2 3 ]
data: [ 0 1 2 3 4 5 ]

shape: [ 1 2 ]
data: [ 1.62673e-37 0 ]

The expected output for the third call was:

shape: [ 2 ]
data: [ 1 2 ]

A workaround is to switch the order of the "create" bindings, so that the array-based one is tried first. However, unless this is intended behavior for some reason, the vector-based version should never be picked if a numpy array is passed. Moreover, the workaround only works if the array is of type np.float32, otherwise the vector-based version is still picked (forcecast seems to have no effect).

This might be related to #1392.

simleo avatar Dec 13 '19 13:12 simleo


#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <vector>
#include <iostream>

namespace py = pybind11;

class Tensor {
public:
    int size;
    std::vector<int> shape;
    float *data;
    Tensor(const std::vector<int>& shape) {
        this->shape = shape;
        size = 1;
        for (size_t i = 0; i < shape.size(); ++i) {
            size *= shape[i];
        }
        data = new float[size];
    }
    void info() {
        std::cout << "shape: [ ";
        for (size_t i = 0; i < shape.size(); ++i) {
            std::cout << shape[i] << " ";
        }
        std::cout << "]" << std::endl;
        std::cout << "data: [ ";
        for (int i = 0; i < size; ++i) {
            std::cout << data[i] << " ";
        }
        std::cout << "]" << std::endl;
    }
};

Tensor* create(const std::vector<int>& shape) {
    return new Tensor(shape);
}

PYBIND11_MODULE(_ext, m) {
    py::class_<Tensor>(m, "Tensor")
        .def(py::init<const std::vector<int>&>())
        .def("info", &Tensor::info);

    // Define the vector-based version (takes std::vector<int>)
    m.def("create", &create, "create(list) --> Tensor", py::arg("shape"));

    // Define the numpy-based version (takes numpy array)
    m.def("create", [](py::array_t<float, py::array::c_style | py::array::forcecast> array) -> Tensor* {
        py::buffer_info info = array.request();
        std::vector<int> shape(info.shape.begin(), info.shape.end());
        Tensor* t = new Tensor(shape);
        std::copy((float*)info.ptr, ((float*)info.ptr) + t->size, t->data);
        return t;
    }, "create(array) --> Tensor", py::arg("array"));
}

ljluestc avatar Dec 24 '24 23:12 ljluestc