gtsam/wrap/Method.cpp

206 lines
7.5 KiB
C++
Raw Normal View History

/* ----------------------------------------------------------------------------
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
* Atlanta, Georgia 30332-0415
* All Rights Reserved
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
* See LICENSE for the license information
* -------------------------------------------------------------------------- */
/**
* @file Method.ccp
* @author Frank Dellaert
2012-07-13 06:28:28 +08:00
* @author Richard Roberts
**/
#include "Method.h"
#include "Class.h"
#include "utilities.h"
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <fstream>
using namespace std;
using namespace wrap;
/* ************************************************************************* */
bool Method::addOverload(Str name, const ArgumentList& args,
const ReturnValue& retVal, bool is_const,
boost::optional<const Qualified> instName,
bool verbose) {
bool first = MethodBase::addOverload(name, args, retVal, instName, verbose);
if (first)
is_const_ = is_const;
else if (is_const && !is_const_)
throw std::runtime_error(
"Method::addOverload now designated as const whereas before it was "
"not");
else if (!is_const && is_const_)
throw std::runtime_error(
"Method::addOverload now designated as non-const whereas before it "
"was");
return first;
}
/* ************************************************************************* */
void Method::proxy_header(FileWriter& proxyFile) const {
proxyFile.oss << " function varargout = " << matlabName()
<< "(this, varargin)\n";
}
/* ************************************************************************* */
string Method::wrapper_call(FileWriter& wrapperFile, Str cppClassName,
Str matlabUniqueName,
const ArgumentList& args) const {
// check arguments
// extra argument obj -> nargin-1 is passed !
// example: checkArguments("equals",nargout,nargin-1,2);
2014-11-30 02:34:46 +08:00
wrapperFile.oss << " checkArguments(\"" << matlabName()
<< "\",nargout,nargin-1," << args.size() << ");\n";
// get class pointer
// example: auto obj = unwrap_shared_ptr< Test >(in[0], "Test");
wrapperFile.oss << " auto obj = unwrap_shared_ptr<" << cppClassName
<< ">(in[0], \"ptr_" << matlabUniqueName << "\");" << endl;
// unwrap arguments, see Argument.cpp, we start at 1 as first is obj
args.matlab_unwrap(wrapperFile, 1);
// call method and wrap result
// example: out[0]=wrap<bool>(obj->return_field(t));
string expanded = "obj->" + name_;
if (templateArgValue_)
expanded += ("<" + templateArgValue_->qualifiedName("::") + ">");
return expanded;
}
/* ************************************************************************* */
2016-09-12 06:14:19 +08:00
void Method::emit_cython_pxd(FileWriter& file, const Class& cls) const {
for (size_t i = 0; i < nrOverloads(); ++i) {
2016-11-30 18:56:07 +08:00
file.oss << " ";
returnVals_[i].emit_cython_pxd(file, cls.pxdClassName(), cls.templateArgs);
2017-08-07 08:26:12 +08:00
const string renamed = pyRename(name_);
if (renamed != name_) {
file.oss << pyRename(name_) + " \"" + name_ + "\"" << "(";
} else {
file.oss << name_ << "(";
}
argumentList(i).emit_cython_pxd(file, cls.pxdClassName(), cls.templateArgs);
file.oss << ")";
// if (is_const_) file.oss << " const";
file.oss << " except +";
file.oss << "\n";
}
}
/* ************************************************************************* */
void Method::emit_cython_pyx_no_overload(FileWriter& file,
const Class& cls) const {
string funcName = pyRename(name_);
// leverage python's special treatment for print
if (funcName == "print_") {
file.oss << " def __repr__(self):\n";
2017-07-28 10:32:27 +08:00
file.oss << " strBuf = RedirectCout()\n";
file.oss << " self.print_('')\n";
file.oss << " return strBuf.str()\n";
}
// Function definition
2016-11-30 18:56:07 +08:00
file.oss << " def " << funcName;
// modify name of function instantiation as python doesn't allow overloads
// e.g. template<T={A,B,C}> funcName(...) --> funcNameA, funcNameB, funcNameC
if (templateArgValue_) file.oss << templateArgValue_->pyxClassName();
// function arguments
file.oss << "(self";
if (argumentList(0).size() > 0) file.oss << ", ";
argumentList(0).emit_cython_pyx(file);
file.oss << "):\n";
/// Call cython corresponding function and return
convert numpy input params to dtype float and order 'F' automatically using numpy.astype(...). No copy if the params are already in the correct dtype and storage order. For a function f(Matrix A, Matrix B), simply wrapping it to pyx as f(A.astype(float, order='F', copy=False), B.astype(float, order='F', copy=False)) won't work. It produces a strange side-effect that the content of A is overwritten by B and the two inputs are the same (data address) inside the function! This is because Cython decreases the ref count for the temporary variable resulted from A.astype(...) before generates the wrap for B.astype(...). Hence, the A.astype temp var is probably reused for B.astype, and they were pointing to the same data address. For that reason, we have to go a longer route and wrap it as: A = A.astype(float, order='F', copy=False) B = B.astype(float, order='F', copy=False) f(A, B) For future ref., here is a sample of the wrongly generated code that wraps the JacobianFactor constructor: Jacobian(Key i1, Matrix A1, Key i2, Matrix A2, Vector b, noiseModel::Diagonal model) Wrongly wrapped pyx code: self.shared_CJacobianFactor_ = shared_ptr[CJacobianFactor](new CJacobianFactor(i1, <MatrixXd>(Map[MatrixXd](A1.astype(float, order='F',copy=False)), i2, <MatrixXd>(Map[MatrixXd](A2.astype(float, order='F', copy=False)), <VectorXd>(Map[VectorXd](b.astype(float, order='F', copy=False))), model.shared_CnoiseModel_Diagonal_)) The problematic Cython generated CPP code with a comment on the problematic line: ///////////////////////////////////////// // WRONG VERSION ///////////////////////////////////////// __pyx_t_12 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_A1), __pyx_n_s_astype); if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_12); __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_4); __Pyx_INCREF(((PyObject *)(&PyFloat_Type))); __Pyx_GIVEREF(((PyObject *)(&PyFloat_Type))); PyTuple_SET_ITEM(__pyx_t_4, 0, ((PyObject *)(&PyFloat_Type))); __pyx_t_5 = PyDict_New(); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyDict_SetItem(__pyx_t_5, __pyx_n_s_order, __pyx_n_s_F) < 0) __PYX_ERR(0, 2107, __pyx_L1_error) if (PyDict_SetItem(__pyx_t_5, __pyx_n_s_copy, Py_False) < 0) __PYX_ERR(0, 2107, __pyx_L1_error) __pyx_t_13 = __Pyx_PyObject_Call(__pyx_t_12, __pyx_t_4, __pyx_t_5); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_13); __Pyx_DECREF(__pyx_t_12); __pyx_t_12 = 0; __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; if (!(likely(((__pyx_t_13) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_13, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 2107, __pyx_L1_error) try { __pyx_t_14 = eigency::Map<Eigen::MatrixXd> (((PyArrayObject *)__pyx_t_13)); } catch(...) { __Pyx_CppExn2PyErr(); __PYX_ERR(0, 2107, __pyx_L1_error) } /////////////////////////////////////////////// __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; //<------- Problematic line!!! Killing this will result in the correct result! /////////////////////////////////////////////// __pyx_t_13 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_A2), __pyx_n_s_astype); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_13); __pyx_t_5 = PyTuple_New(1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); __Pyx_INCREF(((PyObject *)(&PyFloat_Type))); __Pyx_GIVEREF(((PyObject *)(&PyFloat_Type))); PyTuple_SET_ITEM(__pyx_t_5, 0, ((PyObject *)(&PyFloat_Type))); __pyx_t_4 = PyDict_New(); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_4); if (PyDict_SetItem(__pyx_t_4, __pyx_n_s_order, __pyx_n_s_F) < 0) __PYX_ERR(0, 2107, __pyx_L1_error) if (PyDict_SetItem(__pyx_t_4, __pyx_n_s_copy, Py_False) < 0) __PYX_ERR(0, 2107, __pyx_L1_error) __pyx_t_12 = __Pyx_PyObject_Call(__pyx_t_13, __pyx_t_5, __pyx_t_4); if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_12); __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; if (!(likely(((__pyx_t_12) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_12, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 2107, __pyx_L1_error) try { __pyx_t_15 = eigency::Map<Eigen::MatrixXd> (((PyArrayObject *)__pyx_t_12)); } catch(...) { __Pyx_CppExn2PyErr(); __PYX_ERR(0, 2107, __pyx_L1_error) } __Pyx_DECREF(__pyx_t_12); __pyx_t_12 = 0; __pyx_t_12 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_b), __pyx_n_s_astype); if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_12); __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_4); __Pyx_INCREF(((PyObject *)(&PyFloat_Type))); __Pyx_GIVEREF(((PyObject *)(&PyFloat_Type))); PyTuple_SET_ITEM(__pyx_t_4, 0, ((PyObject *)(&PyFloat_Type))); __pyx_t_5 = PyDict_New(); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyDict_SetItem(__pyx_t_5, __pyx_n_s_order, __pyx_n_s_F) < 0) __PYX_ERR(0, 2107, __pyx_L1_error) if (PyDict_SetItem(__pyx_t_5, __pyx_n_s_copy, Py_False) < 0) __PYX_ERR(0, 2107, __pyx_L1_error) __pyx_t_13 = __Pyx_PyObject_Call(__pyx_t_12, __pyx_t_4, __pyx_t_5); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 2107, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_13); __Pyx_DECREF(__pyx_t_12); __pyx_t_12 = 0; __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; if (!(likely(((__pyx_t_13) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_13, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 2107, __pyx_L1_error) try { __pyx_t_16 = eigency::Map<Eigen::VectorXd> (((PyArrayObject *)__pyx_t_13)); } catch(...) { __Pyx_CppExn2PyErr(); __PYX_ERR(0, 2107, __pyx_L1_error) } __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; try { __pyx_t_17 = new gtsam::JacobianFactor(__pyx_v_i1, ((Eigen::MatrixXd)__pyx_t_14), __pyx_v_i2, ((Eigen::MatrixXd)__pyx_t_15), ((Eigen::VectorXd)__pyx_t_16), __pyx_v_model->shared_CnoiseModel_Diagonal_); } catch(...) { __Pyx_CppExn2PyErr(); __PYX_ERR(0, 2107, __pyx_L1_error) } __pyx_v_self->shared_CJacobianFactor_ = boost::shared_ptr<gtsam::JacobianFactor> (__pyx_t_17);
2017-03-16 01:47:11 +08:00
file.oss << argumentList(0).pyx_convertEigenTypeAndStorageOrder(" ");
string caller = "self." + cls.shared_pxd_obj_in_pyx() + ".get()";
string ret = pyx_functionCall(caller, funcName, 0);
if (!returnVals_[0].isVoid()) {
2016-11-30 18:56:07 +08:00
file.oss << " cdef " << returnVals_[0].pyx_returnType()
<< " ret = " << ret << "\n";
2016-11-30 18:56:07 +08:00
file.oss << " return " << returnVals_[0].pyx_casting("ret") << "\n";
} else {
2016-11-30 18:56:07 +08:00
file.oss << " " << ret << "\n";
}
}
/* ************************************************************************* */
void Method::emit_cython_pyx(FileWriter& file, const Class& cls) const {
string funcName = pyRename(name_);
// For template function: modify name of function instantiation as python
// doesn't allow overloads
// e.g. template<T={A,B,C}> funcName(...) --> funcNameA, funcNameB, funcNameC
string instantiatedName =
(templateArgValue_) ? funcName + templateArgValue_->pyxClassName() :
funcName;
size_t N = nrOverloads();
// It's easy if there's no overload
if (N == 1) {
emit_cython_pyx_no_overload(file, cls);
return;
}
// Dealing with overloads..
2016-11-30 18:56:07 +08:00
file.oss << " def " << instantiatedName << "(self, *args, **kwargs):\n";
file.oss << " cdef list __params\n";
// Define return values for all possible overloads
vector<string> return_type; // every overload has a return type, possibly void
map<string, string> return_value; // we only define one return value for every distinct type
size_t j = 1;
for (size_t i = 0; i < nrOverloads(); ++i) {
if (returnVals_[i].isVoid()) {
return_type.push_back("void");
} else {
const string type = returnVals_[i].pyx_returnType();
return_type.push_back(type);
if (return_value.count(type) == 0) {
const string value = "return_value_" + to_string(j++);
return_value[type] = value;
file.oss << " cdef " << type << " " << value << "\n";
}
}
}
for (size_t i = 0; i < nrOverloads(); ++i) {
ArgumentList args = argumentList(i);
file.oss << " try:\n";
file.oss << pyx_resolveOverloadParams(args, false, 3); // lazy: always return None even if it's a void function
/// Call corresponding cython function
file.oss << args.pyx_convertEigenTypeAndStorageOrder(" ");
string caller = "self." + cls.shared_pxd_obj_in_pyx() + ".get()";
string call = pyx_functionCall(caller, funcName, i);
2016-11-22 06:14:30 +08:00
if (!returnVals_[i].isVoid()) {
const string type = return_type[i];
const string value = return_value[type];
file.oss << " " << value << " = " << call << "\n";
file.oss << " return " << returnVals_[i].pyx_casting(value)
<< "\n";
} else {
file.oss << " " << call << "\n";
file.oss << " return\n";
}
2017-12-03 10:43:18 +08:00
file.oss << " except (AssertionError, ValueError):\n";
file.oss << " pass\n";
}
file.oss
2019-01-21 05:52:44 +08:00
<< " raise TypeError('Incorrect arguments or types for method call.')\n\n";
}
/* ************************************************************************* */