Skip to content

Commit 5501496

Browse files
Merge pull request #26 from EricCousineau-TRI/issue/drake_9886
dtype object: Ensure fixed-size vectors are indexed correctly
2 parents 25731f6 + 14032e7 commit 5501496

File tree

3 files changed

+51
-22
lines changed

3 files changed

+51
-22
lines changed

include/pybind11/eigen.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,9 @@ template <typename props> handle eigen_array_cast(typename props::Type const &sr
248248
nullptr,
249249
empty_base
250250
);
251+
constexpr bool is_row = props::fixed_rows && props::rows == 1;
251252
for (ssize_t i = 0; i < src.size(); ++i) {
252-
const Scalar src_val = props::fixed_rows ? src(0, i) : src(i, 0);
253+
const Scalar src_val = is_row ? src(0, i) : src(i, 0);
253254
auto value_ = reinterpret_steal<object>(make_caster<Scalar>::cast(src_val, policy, empty_base));
254255
if (!value_)
255256
return handle();

tests/test_eigen.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ using MatrixX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
2828

2929
typedef Eigen::Matrix<ADScalar, Eigen::Dynamic, 1> VectorXADScalar;
3030
typedef Eigen::Matrix<ADScalar, 1, Eigen::Dynamic> VectorXADScalarR;
31+
typedef Eigen::Matrix<ADScalar, 5, 1> Vector5ADScalar;
32+
typedef Eigen::Matrix<ADScalar, 1, 6> Vector6ADScalarR;
3133
PYBIND11_NUMPY_OBJECT_DTYPE(ADScalar);
3234

3335
// Sets/resets a testing reference matrix to have values of 10*r + c, where r and c are the
@@ -112,14 +114,17 @@ TEST_SUBMODULE(eigen, m) {
112114
// various tests
113115
m.def("double_col", [](const Eigen::VectorXf &x) -> Eigen::VectorXf { return 2.0f * x; });
114116
m.def("double_adscalar_col", [](const VectorXADScalar &x) -> VectorXADScalar { return 2.0f * x; });
117+
m.def("double_adscalar_col5", [](const Vector5ADScalar &x) -> Vector5ADScalar { return 2.0f * x; });
115118
m.def("double_row", [](const Eigen::RowVectorXf &x) -> Eigen::RowVectorXf { return 2.0f * x; });
116119
m.def("double_adscalar_row", [](const VectorXADScalarR &x) -> VectorXADScalarR { return 2.0f * x; });
120+
m.def("double_adscalar_row6", [](const Vector6ADScalarR &x) -> Vector6ADScalarR { return 2.0f * x; });
117121
m.def("double_complex", [](const Eigen::VectorXcf &x) -> Eigen::VectorXcf { return 2.0f * x; });
118122
m.def("double_threec", [](py::EigenDRef<Eigen::Vector3f> x) { x *= 2; });
119123
m.def("double_threer", [](py::EigenDRef<Eigen::RowVector3f> x) { x *= 2; });
120124
m.def("double_mat_cm", [](Eigen::MatrixXf x) -> Eigen::MatrixXf { return 2.0f * x; });
121125
m.def("double_mat_rm", [](DenseMatrixR x) -> DenseMatrixR { return 2.0f * x; });
122126

127+
123128
// test_eigen_ref_to_python
124129
// Different ways of passing via Eigen::Ref; the first and second are the Eigen-recommended
125130
m.def("cholesky1", [](Eigen::Ref<MatrixXdR> x) -> Eigen::MatrixXd { return x.llt().matrixL(); });
@@ -270,8 +275,20 @@ TEST_SUBMODULE(eigen, m) {
270275
.def("value", [](const ADScalar & self) {
271276
return self.value();
272277
})
278+
.def("__repr__", [](const ADScalar& self) {
279+
return py::str("<ADScalar {} deriv={}>").format(self.value(), self.derivatives());
280+
})
273281
;
274282

283+
m.def("equal_to", [](double a, double b) { return a == b; });
284+
// AutDiff's operator== only compares the value; we should compare the full scalar.
285+
m.def("equal_to", [](const ADScalar& a, const ADScalar& b) {
286+
auto& a_d = a.derivatives();
287+
auto& b_d = b.derivatives();
288+
return a.value() == b.value() && a_d.size() == b_d.size() &&
289+
(a_d.array() == b_d.array()).all();
290+
});
291+
275292
// test_special_matrix_objects
276293
// Returns a DiagonalMatrix with diagonal (1,2,3,...)
277294
m.def("incr_diag", [](int k) {

tests/test_eigen.py

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -151,41 +151,52 @@ def test_nonunit_stride_from_python():
151151
np.testing.assert_array_equal(counting_mat, [[0., 2, 2], [6, 16, 10], [6, 14, 8]])
152152

153153

154-
def conv_double_to_adscalar(arr, vice_versa=False):
155-
flat_arr = arr.flatten()
156-
new_arr = np.zeros(flat_arr.shape, dtype=object)
154+
def float_to_adscalar(arr, deriv):
155+
arr = np.asarray(arr)
156+
assert arr.dtype == float
157+
new_arr = [m.AutoDiffXd(x, deriv) for x in arr.flat]
158+
return np.array(new_arr).reshape(arr.shape)
157159

158-
for i in range(0, flat_arr.shape[0]):
159-
if vice_versa:
160-
new_arr[i] = flat_arr[i].value()
161-
else:
162-
new_arr[i] = m.AutoDiffXd(flat_arr[i], np.ones(1))
163160

164-
return new_arr.reshape(arr.shape)
161+
def adscalar_to_float(arr):
162+
arr = np.asarray(arr)
163+
assert arr.dtype == object
164+
new_arr = [x.value() for x in arr.flat]
165+
return np.array(new_arr).reshape(arr.shape)
166+
167+
168+
def check_array(a, b):
169+
a, b = (np.asarray(x) for x in (a, b))
170+
assert a.shape == b.shape and a.dtype == b.dtype
171+
for index, (ai, bi) in enumerate(zip(a.flat, b.flat)):
172+
assert m.equal_to(ai, bi), index
165173

166174

167175
def test_eigen_passing_adscalar():
168-
adscalar_mat = conv_double_to_adscalar(ref)
176+
assert m.equal_to(1., 1.)
177+
assert not m.equal_to(1., 1.1)
178+
assert m.equal_to(m.AutoDiffXd(0, [1.]), m.AutoDiffXd(0, [1.]))
179+
assert not m.equal_to(m.AutoDiffXd(0, [1.]), m.AutoDiffXd(0, [1.1]))
180+
181+
adscalar_mat = float_to_adscalar(ref, deriv=[1.])
169182
adscalar_vec_col = adscalar_mat[:, 0]
170183
adscalar_vec_row = adscalar_mat[0, :]
171184

172-
# Checking if a Python vector is getting doubled, when passed into a dynamic
185+
# Checking if a Python vector is getting doubled, when passed into a dynamic or fixed
173186
# row or col vector in Eigen.
174-
adscalar_double_col = m.double_adscalar_col(adscalar_vec_col)
175-
adscalar_double_row = m.double_adscalar_row(adscalar_vec_row)
176-
np.testing.assert_array_equal(conv_double_to_adscalar(adscalar_double_col, vice_versa=True),
177-
2 * ref[:, 0])
178-
np.testing.assert_array_equal(conv_double_to_adscalar(adscalar_double_row, vice_versa=True),
179-
2 * ref[0, :])
187+
double_adscalar_mat = float_to_adscalar(2 * ref, deriv=[2.])
188+
check_array(m.double_adscalar_col(adscalar_vec_col), double_adscalar_mat[:, 0])
189+
check_array(m.double_adscalar_col5(adscalar_vec_col), double_adscalar_mat[:, 0])
190+
check_array(m.double_adscalar_row(adscalar_vec_row), double_adscalar_mat[0, :])
191+
check_array(m.double_adscalar_row6(adscalar_vec_row), double_adscalar_mat[0, :])
180192

181193
# Adding 7 to the a dynamic matrix using reference.
182-
incremented_adscalar_mat = conv_double_to_adscalar(m.incr_adscalar_matrix(adscalar_mat, 7.),
183-
vice_versa=True)
184-
np.testing.assert_array_equal(incremented_adscalar_mat, ref + 7)
194+
incr_adscalar_mat = float_to_adscalar(ref + 7, deriv=[1.])
195+
check_array(m.incr_adscalar_matrix(adscalar_mat, 7.), incr_adscalar_mat)
185196
# The original adscalar_mat remains unchanged in spite of passing by reference, since
186197
# `Eigen::Ref<const CType>` permits copying, and copying is the only valid operation for
187198
# `dtype=object`.
188-
np.testing.assert_array_equal(conv_double_to_adscalar(adscalar_mat, vice_versa=True), ref)
199+
check_array(adscalar_to_float(adscalar_mat), ref)
189200

190201
# Changes in Python are not reflected in C++ when internal_reference is returned.
191202
# These conversions should be disabled at runtime.

0 commit comments

Comments
 (0)