1

我有分别使用 Boost Multiprecision 和 Python 的 mpmath 的经验。

当要让两者进行通信时(例如在 C++ 中创建 Python 扩展),我的尝试总是涉及某种浪费的浮点到字符串和字符串到浮点的转换。

我的问题是:是否有可能使两者以更高效(和优雅)的方式进行通信?我的意思是,有没有办法直接让 C++ Boost Multiprecision 从 Pythonmpmath.mpf对象加载和导出,就像 C 的mppviapybind11一样?

我一直在寻找这个。我发现的唯一其他类似问题是关于使用 Boost Multiprecision 导出到 Python(通常)pybind11,而不是mpmath直接导出到对象。在那个问题中,OP 最终使用了我试图避免的相同方法(即,在与 C++ 和 Python 通信时从/到字符串转换)。

4

1 回答 1

0

这仅部分回答了您的问题。因为直接的答案是:不,如果没有浪费地转换为字符串,就不可能以干净的方式进行,因为mpmath它是一个纯粹的python库,没有任何部分写入Cor C++,因此即使您尝试跳过“浪费的转换”寻求使用某种二进制兼容性,您的代码将非常脆弱:每当某些pythonmpmath内部结构发生如此轻微的变化时,它都会中断。

但是我需要完全相同的东西。所以我决定注册一个自动转换,通过boost::python它使用字符串检查和转换。实际上在 python 中,你也可以mpmath.mpf从字符串创建对象,所以它非常相似,除了在下面的代码中它更快,因为它是写在里面C++的。

所以这对我有用:

#include <boost/python.hpp>
#include <iostream>
#include <limits>
#include <sstream>
#include <boost/math/constants/constants.hpp>
#include <boost/multiprecision/cpp_bin_float.hpp>
namespace py = ::boost::python;
using Prec80 = boost::multiprecision::number<boost::multiprecision::cpp_bin_float<80>>;

template<typename ArbitraryReal>
struct ArbitraryReal_to_python {
    static PyObject* convert(const ArbitraryReal& val){
        std::stringstream ss{};
        ss << std::setprecision(std::numeric_limits<ArbitraryReal>::digits10+1) << val;
        py::object mpmath = py::import("mpmath");
        mpmath.attr("mp").attr("dps")=int(std::numeric_limits<ArbitraryReal>::digits10+1);
        py::object result = mpmath.attr("mpf")(ss.str());
        return boost::python::incref( result.ptr() );
    }
};

template<typename ArbitraryReal>
struct ArbitraryReal_from_python {
    ArbitraryReal_from_python(){
         boost::python::converter::registry::push_back(&convertible,&construct,boost::python::type_id<ArbitraryReal>());
    }
    static void* convertible(PyObject* obj_ptr){
        // Accept whatever python is able to convert into float
        // This works with mpmath numbers. However if you want to accept strings as numbers this checking code can be a little longer to verify if string is a valid number.
        double check = PyFloat_AsDouble(obj_ptr);
        return (PyErr_Occurred()==nullptr) ? obj_ptr : nullptr;
    }
    static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data){
        std::istringstream ss{ py::call_method<std::string>(obj_ptr, "__str__") };
        void* storage=((boost::python::converter::rvalue_from_python_storage<ArbitraryReal>*)(data))->storage.bytes;
        new (storage) ArbitraryReal;
        ArbitraryReal* val=(ArbitraryReal*)storage;
        ss >> *val;
        data->convertible=storage;
    }
};


struct Var
{
    Prec80 value{"-71.23"};
    Prec80 get() const   { return value; };
    void set(Prec80 val) { value = val;  };
};

BOOST_PYTHON_MODULE(pysmall)
{
    ArbitraryReal_from_python<Prec80>();
    py::to_python_converter<Prec80,ArbitraryReal_to_python<Prec80>>();

    py::class_<Var>("Var" )
        .add_property("val", &Var::get, &Var::set);
}

现在你用这个命令编译这段代码:

g++ -O1 -g pysmall.cpp -o pysmall.so -std=gnu++17 -fPIC -shared -I/usr/include/python3.7m/ -lboost_python37 -lpython3.7m -Wl,-soname,"pysmall.so"

这是一个示例python会话:

In [1]: import pysmall
In [2]: a=pysmall.Var()
In [3]: a.val
Out[3]: mpf('-71.2299999999999999999999999999999999999999999999999999999999999999999999999999997072')
In [4]: a.val=123.12
In [5]: a.val
Out[5]: mpf('123.120000000000000000000000000000000000000000000000000000000000000000000000000000003')

C++代码不关心 mpmath 是否已经在 python 中导入。如果是,则获取现有库句柄,如果不是,则导入它。如果您发现此代码段有任何改进空间,请告诉我!

在我写这篇文章时,这里有一些有用的参考资料:

  1. https://misspent.wordpress.com/2009/09/27/how-to-write-boost-python-converters/
  2. https://github.com/bluescarni/mppp/blob/master/include/mp%2B%2B/extra/pybind11.hpp(但我不想用pybind11,只是boost::python

编辑:我现在已经在 YADE 中完成了它的实现它适用于 EIGEN 和 CGAL 库。关于这个问题的部分在文件ToFromPythonConverter.hpp

于 2019-10-25T13:48:54.347 回答