Skip to content

Commit

Permalink
Add more Python wrappers (#229)
Browse files Browse the repository at this point in the history
* Initializer list constructors
* Zero() and Ones() static factory functions
* shape attribute for matrix sizes
* Block() free function
  • Loading branch information
calcmogul authored Dec 12, 2023
1 parent 66b9d16 commit eb3c431
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 28 deletions.
33 changes: 33 additions & 0 deletions include/sleipnir/autodiff/VariableMatrix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ class SLEIPNIR_DLLEXPORT VariableMatrix {
VariableMatrix(
std::initializer_list<std::initializer_list<Variable>> list); // NOLINT

/**
* Constructs a scalar VariableMatrix from a nested list of doubles.
*
* This overload is for Python bindings only.
*
* @param list The nested list of Variables.
*/
VariableMatrix(std::vector<std::vector<double>> list); // NOLINT

/**
* Constructs a scalar VariableMatrix from a nested list of Variables.
*
* This overload is for Python bindings only.
*
* @param list The nested list of Variables.
*/
VariableMatrix(std::vector<std::vector<Variable>> list); // NOLINT

/**
* Constructs a VariableMatrix from an Eigen matrix.
*/
Expand Down Expand Up @@ -573,4 +591,19 @@ VariableMatrix CwiseReduce(const VariableMatrix& lhs, const VariableMatrix& rhs,
SLEIPNIR_DLLEXPORT VariableMatrix
Block(std::initializer_list<std::initializer_list<VariableMatrix>> list);

/**
* Assemble a VariableMatrix from a nested list of blocks.
*
* Each row's blocks must have the same height, and the assembled block rows
* must have the same width. For example, for the block matrix [[A, B], [C]] to
* be constructible, the number of rows in A and B must match, and the number of
* columns in [A, B] and [C] must match.
*
* This overload is for Python bindings only.
*
* @param list The nested list of blocks.
*/
SLEIPNIR_DLLEXPORT VariableMatrix
Block(std::vector<std::vector<VariableMatrix>> list);

} // namespace sleipnir
51 changes: 34 additions & 17 deletions jormungandr/cpp/autodiff/BindVariableMatrices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <pybind11/eigen.h>
#include <pybind11/functional.h>
#include <pybind11/operators.h>
#include <pybind11/stl.h>
#include <sleipnir/autodiff/Variable.hpp>
#include <sleipnir/autodiff/VariableBlock.hpp>
#include <sleipnir/autodiff/VariableMatrix.hpp>
Expand All @@ -29,16 +30,37 @@ void BindVariableMatrices(py::module_& autodiff) {
"VariableBlock"};

BindVariableMatrix(autodiff, variable_matrix);

autodiff.def(
"cwise_reduce",
[](const VariableMatrix& lhs, const VariableMatrix& rhs,
const std::function<Variable(const Variable&, const Variable&)> func) {
return CwiseReduce(lhs, rhs, func);
});

autodiff.def(
"block",
static_cast<VariableMatrix (*)(std::vector<std::vector<VariableMatrix>>)>(
&Block));

BindVariableBlock(autodiff, variable_block);

// TODO: Wrap sleipnir::Block()
autodiff.def(
"cwise_reduce",
[](const VariableBlock<VariableMatrix>& lhs,
const VariableBlock<VariableMatrix>& rhs,
const std::function<Variable(const Variable&, const Variable&)> func) {
return CwiseReduce(lhs, rhs, func);
});
}

void BindVariableMatrix(py::module_& autodiff,
py::class_<VariableMatrix>& variable_matrix) {
variable_matrix.def(py::init<>());
variable_matrix.def(py::init<int, int>());
variable_matrix.def(py::init<double>());
variable_matrix.def(py::init<std::vector<std::vector<double>>>());
variable_matrix.def(py::init<std::vector<std::vector<Variable>>>());
variable_matrix.def(py::init<const Variable&>());
variable_matrix.def(py::init<const VariableBlock<VariableMatrix>&>());
variable_matrix.def("set",
Expand Down Expand Up @@ -361,6 +383,10 @@ void BindVariableMatrix(py::module_& autodiff,
variable_matrix.def_property_readonly("T", &VariableMatrix::T);
variable_matrix.def("rows", &VariableMatrix::Rows);
variable_matrix.def("cols", &VariableMatrix::Cols);
variable_matrix.def_property_readonly(
"shape", [](const VariableMatrix& self) {
return py::make_tuple(self.Rows(), self.Cols());
});
variable_matrix.def("value",
static_cast<double (VariableMatrix::*)(int, int) const>(
&VariableMatrix::Value));
Expand All @@ -375,6 +401,8 @@ void BindVariableMatrix(py::module_& autodiff,
const std::function<Variable(const Variable&)>& func) {
return self.CwiseTransform(func);
});
variable_matrix.def_static("zero", &VariableMatrix::Zero);
variable_matrix.def_static("ones", &VariableMatrix::Ones);
variable_matrix.def(py::self == py::self);
variable_matrix.def(py::self == double());
variable_matrix.def(py::self == int());
Expand Down Expand Up @@ -463,13 +491,6 @@ void BindVariableMatrix(py::module_& autodiff,
return lhs >= rhs;
},
py::is_operator());

autodiff.def(
"cwise_reduce",
[](const VariableMatrix& lhs, const VariableMatrix& rhs,
const std::function<Variable(const Variable&, const Variable&)> func) {
return CwiseReduce(lhs, rhs, func);
});
}

void BindVariableBlock(
Expand Down Expand Up @@ -769,6 +790,10 @@ void BindVariableBlock(
variable_block.def_property_readonly("T", &VariableBlock<VariableMatrix>::T);
variable_block.def("rows", &VariableBlock<VariableMatrix>::Rows);
variable_block.def("cols", &VariableBlock<VariableMatrix>::Cols);
variable_block.def_property_readonly(
"shape", [](const VariableBlock<VariableMatrix>& self) {
return py::make_tuple(self.Rows(), self.Cols());
});
variable_block.def(
"value",
static_cast<double (VariableBlock<VariableMatrix>::*)(int, int) const>(
Expand All @@ -782,7 +807,7 @@ void BindVariableBlock(
static_cast<Eigen::MatrixXd (VariableBlock<VariableMatrix>::*)() const>(
&VariableBlock<VariableMatrix>::Value));
variable_block.def("cwise_transform",
[](const VariableMatrix& self,
[](const VariableBlock<VariableMatrix>& self,
const std::function<Variable(const Variable&)>& func) {
return self.CwiseTransform(func);
});
Expand Down Expand Up @@ -812,14 +837,6 @@ void BindVariableBlock(
variable_block.def(double() >= py::self);
variable_block.def(int() >= py::self);
py::implicitly_convertible<VariableBlock<VariableMatrix>, VariableMatrix>();

autodiff.def(
"cwise_reduce",
[](const VariableBlock<VariableMatrix>& lhs,
const VariableBlock<VariableMatrix>& rhs,
const std::function<Variable(const Variable&, const Variable&)> func) {
return CwiseReduce(lhs, rhs, func);
});
}

} // namespace sleipnir
46 changes: 40 additions & 6 deletions jormungandr/test/autodiff/variable_matrix_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ def test_assignment_to_default():

assert mat.rows() == 0
assert mat.cols() == 0
assert mat.shape == (0, 0)

mat = VariableMatrix(2, 2)

assert mat.rows() == 2
assert mat.cols() == 2
assert mat.shape == (2, 2)
assert mat[0, 0] == 0.0
assert mat[0, 1] == 0.0
assert mat[1, 0] == 0.0
Expand All @@ -32,8 +34,7 @@ def test_assignment_to_default():

def test_cwise_transform():
# VariableMatrix CwiseTransform
A = VariableMatrix(2, 3)
A.set_value(np.array([[-2.0, -3.0, -4.0], [-5.0, -6.0, -7.0]]))
A = VariableMatrix([[-2.0, -3.0, -4.0], [-5.0, -6.0, -7.0]])

result1 = A.cwise_transform(autodiff.abs)
expected1 = np.array([[2.0, 3.0, 4.0], [5.0, 6.0, 7.0]])
Expand All @@ -56,11 +57,44 @@ def test_cwise_transform():
assert (expected2 == result2.value()).all()


def test_zero_static_function():
A = VariableMatrix.zero(2, 3)

for row in range(A.rows()):
for col in range(A.cols()):
assert A[row, col].value() == 0.0


def test_ones_static_function():
A = VariableMatrix.ones(2, 3)

for row in range(A.rows()):
for col in range(A.cols()):
assert A[row, col].value() == 1.0


def test_cwise_reduce():
A = VariableMatrix(2, 3)
A.set_value(np.array([[2.0, 3.0, 4.0], [5.0, 6.0, 7.0]]))
B = VariableMatrix(2, 3)
B.set_value(np.array([[8.0, 9.0, 10.0], [11.0, 12.0, 13.0]]))
A = VariableMatrix([[2.0, 3.0, 4.0], [5.0, 6.0, 7.0]])
B = VariableMatrix([[8.0, 9.0, 10.0], [11.0, 12.0, 13.0]])
result = autodiff.cwise_reduce(A, B, lambda a, b: a * b)

assert (np.array([[16.0, 27.0, 40.0], [55.0, 72.0, 91.0]]) == result.value()).all()


def test_block_free_function():
A = VariableMatrix([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
B = VariableMatrix([[7.0], [8.0]])

mat1 = autodiff.block([[A, B]])
expected1 = np.array([[1.0, 2.0, 3.0, 7.0], [4.0, 5.0, 6.0, 8.0]])
assert mat1.shape == (2, 4)
assert (expected1 == mat1.value()).all()

C = VariableMatrix([[9.0, 10.0, 11.0, 12.0]])

mat2 = autodiff.block([[A, B], [C]])
expected2 = np.array(
[[1.0, 2.0, 3.0, 7.0], [4.0, 5.0, 6.0, 8.0], [9.0, 10.0, 11.0, 12.0]]
)
assert mat2.shape == (3, 4)
assert (expected2 == mat2.value()).all()
84 changes: 84 additions & 0 deletions src/autodiff/VariableMatrix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,48 @@ VariableMatrix::VariableMatrix(
}
}

VariableMatrix::VariableMatrix(std::vector<std::vector<double>> list) {
// Get row and column counts for destination matrix
m_rows = list.size();
m_cols = 0;
if (list.size() > 0) {
m_cols = list.begin()->size();
}

// Assert the first and latest column counts are the same
for ([[maybe_unused]] const auto& row : list) {
assert(list.begin()->size() == row.size());
}

m_storage.reserve(Rows() * Cols());
for (const auto& row : list) {
for (const auto& elem : row) {
m_storage.emplace_back(elem);
}
}
}

VariableMatrix::VariableMatrix(std::vector<std::vector<Variable>> list) {
// Get row and column counts for destination matrix
m_rows = list.size();
m_cols = 0;
if (list.size() > 0) {
m_cols = list.begin()->size();
}

// Assert the first and latest column counts are the same
for ([[maybe_unused]] const auto& row : list) {
assert(list.begin()->size() == row.size());
}

m_storage.reserve(Rows() * Cols());
for (const auto& row : list) {
for (const auto& elem : row) {
m_storage.emplace_back(elem);
}
}
}

VariableMatrix::VariableMatrix(const Variable& variable)
: m_rows{1}, m_cols{1} {
m_storage.emplace_back(variable);
Expand Down Expand Up @@ -451,4 +493,46 @@ VariableMatrix Block(
return result;
}

VariableMatrix Block(std::vector<std::vector<VariableMatrix>> list) {
// Get row and column counts for destination matrix
int rows = 0;
int cols = -1;
for (const auto& row : list) {
if (row.size() > 0) {
rows += row.begin()->Rows();
}

// Get number of columns in this row
int latestCols = 0;
for (const auto& elem : row) {
// Assert the first and latest row have the same height
assert(row.begin()->Rows() == elem.Rows());

latestCols += elem.Cols();
}

// If this is the first row, record the column count. Otherwise, assert the
// first and latest column counts are the same.
if (cols == -1) {
cols = latestCols;
} else {
assert(cols == latestCols);
}
}

VariableMatrix result{rows, cols};

int rowOffset = 0;
for (const auto& row : list) {
int colOffset = 0;
for (const auto& elem : row) {
result.Block(rowOffset, colOffset, elem.Rows(), elem.Cols()) = elem;
colOffset += elem.Cols();
}
rowOffset += row.begin()->Rows();
}

return result;
}

} // namespace sleipnir
8 changes: 3 additions & 5 deletions test/src/autodiff/VariableMatrixTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,8 @@ TEST(VariableMatrixTest, CwiseReduce) {
}

TEST(VariableMatrixTest, BlockFreeFunction) {
sleipnir::VariableMatrix A =
Eigen::Matrix<double, 2, 3>{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
sleipnir::VariableMatrix B = Eigen::Matrix<double, 2, 1>{{7.0}, {8.0}};
sleipnir::VariableMatrix A{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
sleipnir::VariableMatrix B{{7.0}, {8.0}};

sleipnir::VariableMatrix mat1 = sleipnir::Block({{A, B}});
Eigen::Matrix<double, 2, 4> expected1{{1.0, 2.0, 3.0, 7.0},
Expand All @@ -139,8 +138,7 @@ TEST(VariableMatrixTest, BlockFreeFunction) {
EXPECT_EQ(4, mat1.Cols());
EXPECT_EQ(expected1, mat1.Value());

sleipnir::VariableMatrix C =
Eigen::Matrix<double, 1, 4>{{9.0, 10.0, 11.0, 12.0}};
sleipnir::VariableMatrix C{{9.0, 10.0, 11.0, 12.0}};

sleipnir::VariableMatrix mat2 = sleipnir::Block({{A, B}, {C}});
Eigen::Matrix<double, 3, 4> expected2{
Expand Down

0 comments on commit eb3c431

Please sign in to comment.