Skip to content

Commit d4b1871

Browse files
authored
Merge pull request #184 from borglab/codex/py-arg-policy-hook
[codex] Add copy-free matrix view hooks
2 parents 41e4e62 + 8049216 commit d4b1871

18 files changed

Lines changed: 471 additions & 131 deletions

gtwrap/matlab_wrapper/mixins.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class CheckMixin:
2020
)
2121
# Ignore the namespace for these datatypes
2222
ignore_namespace: Tuple = ('Matrix', 'Vector', 'Point2', 'Point3')
23+
# Matrix-like view types that can alias MATLAB double matrix storage.
24+
matrix_view_types: Tuple = ('ConstMatrixView', )
2325
# Methods that should be ignored
2426
ignore_methods: Tuple = ('pickle', )
2527
# Methods that should not be wrapped directly
@@ -42,6 +44,7 @@ def can_be_pointer(self, arg_type: parser.Type):
4244
"""
4345
return (arg_type.typename.name not in self.not_ptr_type
4446
and arg_type.typename.name not in self.ignore_namespace
47+
and not self.is_matrix_view(arg_type)
4548
and arg_type.typename.name != 'string')
4649

4750
def is_shared_ptr(self, arg_type: parser.Type):
@@ -67,6 +70,10 @@ def is_ref(self, arg_type: parser.Type):
6770
arg_type.typename.name not in self.not_ptr_type and \
6871
arg_type.is_ref
6972

73+
def is_matrix_view(self, arg_type: parser.Type):
74+
"""Check if `arg_type` should be unwrapped as a matrix view."""
75+
return arg_type.typename.name in self.matrix_view_types
76+
7077
def is_class_enum(self, arg_type: parser.Type, class_: parser.Class):
7178
"""Check if arg_type is an enum in the class `class_`."""
7279
if class_:

gtwrap/matlab_wrapper/wrapper.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(self,
5151
'unsigned char': 'unsigned char',
5252
'Vector': 'double',
5353
'Matrix': 'double',
54+
'ConstMatrixView': 'double',
5455
'int': 'numeric',
5556
'size_t': 'numeric',
5657
'Key': 'numeric',
@@ -69,6 +70,7 @@ def __init__(self,
6970
'Point3': 'double',
7071
'Vector': 'double',
7172
'Matrix': 'double',
73+
'ConstMatrixView': 'double',
7274
'Key': 'numeric',
7375
'bool': 'bool'
7476
}
@@ -354,6 +356,11 @@ def _unwrap_argument(self, arg, arg_id=0, instantiated_class=None):
354356
arg_type = f"{enum_type}"
355357
unwrap = f'unwrap_enum<{enum_type}>(in[{arg_id}]);'
356358

359+
elif self.is_matrix_view(arg.ctype):
360+
arg_type = self._format_type_name(arg.ctype.typename)
361+
unwrap = 'unwrapMatrixView< {ctype} >(in[{id}]);'.format(
362+
ctype=arg_type, id=arg_id)
363+
357364
elif self.is_ref(arg.ctype): # and not constructor:
358365
arg_type = "{ctype}&".format(ctype=ctype_sep)
359366
unwrap = '*unwrap_shared_ptr< {ctype} >(in[{id}], "ptr_{ctype_camel}");'.format(

gtwrap/pybind_wrapper.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,26 @@ class PybindWrapper:
2727
Class to generate binding code for Pybind11 specifically.
2828
"""
2929

30+
ARG_POLICY_SUPPORT = """
31+
#include <type_traits>
32+
33+
namespace gtwrap {
34+
namespace internal {
35+
36+
template <typename T>
37+
struct PyArgPolicy {
38+
static pybind11::arg make(const char* name) { return pybind11::arg(name); }
39+
};
40+
41+
template <typename T>
42+
pybind11::arg py_arg(const char* name) {
43+
return PyArgPolicy<typename std::decay<T>::type>::make(name);
44+
}
45+
46+
} // namespace internal
47+
} // namespace gtwrap
48+
"""
49+
3050
def __init__(self,
3151
module_name,
3252
top_module_namespaces='',
@@ -70,8 +90,11 @@ def _py_args_names(self, args):
7090
default = ' = {arg.default}'.format(arg=arg)
7191
else:
7292
default = ''
73-
argument = 'py::arg("{name}"){default}'.format(
74-
name=arg.name, default='{0}'.format(default))
93+
argument = (
94+
'gtwrap::internal::py_arg<{ctype}>("{name}"){default}'
95+
).format(ctype=arg.ctype.to_cpp(),
96+
name=arg.name,
97+
default='{0}'.format(default))
7598
py_args.append(argument)
7699
return ", " + ", ".join(py_args)
77100
else:
@@ -251,12 +274,13 @@ def _wrap_method(self,
251274
method,
252275
(parser.StaticMethod, instantiator.InstantiatedStaticMethod))
253276
return_void = method.return_type.is_void()
254-
return_ref = getattr(
255-
getattr(method.return_type, 'type1', None), 'is_ref', False)
277+
return_type = getattr(method.return_type, 'type1', None)
278+
return_ref = getattr(return_type, 'is_ref', False)
279+
return_const = getattr(return_type, 'is_const', False)
256280

257281
# For methods returning const T&, use reference_internal policy
258282
# to avoid unnecessary copies and keep the returned reference alive.
259-
if return_ref and is_method:
283+
if return_ref and return_const and is_method:
260284
lambda_ret = ' -> const auto&'
261285
ref_policy = ', py::return_value_policy::reference_internal'
262286
else:
@@ -752,6 +776,8 @@ def wrap_file(self, content, module_name=None, submodules=None):
752776
module_def = "void {0}(py::module_ &m_)".format(module_name)
753777
submodules = []
754778

779+
includes += self.ARG_POLICY_SUPPORT
780+
755781
return self.module_template.format(
756782
module_def=module_def,
757783
module_name=module_name,

matlab.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ extern "C" {
3737
#include <mex.h>
3838
}
3939

40+
#include <limits>
4041
#include <list>
4142
#include <set>
4243
#include <sstream>
@@ -429,6 +430,29 @@ gtsam::Matrix unwrap< gtsam::Matrix >(const mxArray* array) {
429430
return A;
430431
}
431432

433+
// unwrap a MATLAB double matrix as a const Eigen matrix view without copying
434+
template <typename MatrixView>
435+
MatrixView unwrapMatrixView(const mxArray* array) {
436+
if (mxIsDouble(array)==false || mxIsComplex(array) || mxIsSparse(array))
437+
error("unwrapMatrixView: not a full real double matrix");
438+
const mwSize rows = mxGetM(array), cols = mxGetN(array);
439+
if (rows > static_cast<mwSize>(std::numeric_limits<Eigen::Index>::max()) ||
440+
cols > static_cast<mwSize>(std::numeric_limits<Eigen::Index>::max())) {
441+
error("unwrapMatrixView: matrix dimensions exceed Eigen::Index");
442+
}
443+
const Eigen::Index m = static_cast<Eigen::Index>(rows);
444+
const Eigen::Index n = static_cast<Eigen::Index>(cols);
445+
#ifdef DEBUG_WRAP
446+
mexPrintf("unwrapMatrixView called with %lldx%lld argument\n",
447+
static_cast<long long>(m), static_cast<long long>(n));
448+
#endif
449+
using Stride = Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>;
450+
using ConstMatrixMap = Eigen::Map<const gtsam::Matrix, 0, Stride>;
451+
const double* data = static_cast<const double*>(mxGetData(array));
452+
ConstMatrixMap map(data, m, n, Stride(m, 1));
453+
return MatrixView(map);
454+
}
455+
432456
/*
433457
[create_object] creates a MATLAB proxy class object with a mexhandle
434458
in the self property. Matlab does not allow the creation of matlab
@@ -547,4 +571,3 @@ Class* unwrap_ptr(const mxArray* obj, const string& propertyName) {
547571
// static_assert(unwrap_shared_ptr_Matrix_attempted, "Matrix cannot be unwrapped as a shared pointer");
548572
// return Matrix();
549573
//}
550-

0 commit comments

Comments
 (0)