Skip to content

Instantly share code, notes, and snippets.

@mtao
Last active March 16, 2023 15:07
Show Gist options
  • Save mtao/5d8187fd5064dc07138bbf7108313a25 to your computer and use it in GitHub Desktop.
Save mtao/5d8187fd5064dc07138bbf7108313a25 to your computer and use it in GitHub Desktop.
a little explanation of what Eigen::Ref can be used for
#include <Eigen/Dense>
#include <cassert>
#include <iostream>
// the ideal way to implement a cross product function is to depend on eigen's
// builtin
template <typename XDerived, typename YDerived>
auto cross_t(const Eigen::MatrixBase<XDerived>& x,
const Eigen::MatrixBase<YDerived>& y) {
return x.cross(y);
}
// however note that the eigen MatrixBase<>::cross() function has a static
// assert: EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(OtherDerived,3) this states
// that XDerived::RowsAtCompileTime must be 3 (and similar for YDerived)
Eigen::VectorXd cross(const Eigen::VectorXd& x, const Eigen::VectorXd& y) {
// this will activate the compilation failure
// return cross_t(x,y);
// using Eigen::Ref transparently hides the fact that x is a VectorXd by
// filling out the pertinent values the right way in particular
// Eigen::Ref<const Eigen::Vector3d>::RowsAtCompileTime == 3
return cross_t(Eigen::Ref<const Eigen::Vector3d>{x},
Eigen::Ref<const Eigen::Vector3d>{y});
// so how does it do this? presumably this line is equivalent to
return cross_t(Eigen::Map<const Eigen::Vector3d>{x.data()},
Eigen::Map<const Eigen::Vector3d>{y.data()});
// Basically you just take a pointer to the start of x/y and assume the
// next 24 bytes form the data for the vector. Ref uses the input type
// to make sure this is the case
}
// this example (using a Vector3d) is quite mundane as filling out the Map
// object is relatively easy; however consider the case where we're reading into
// rows of an Eigen::MatrixXd as is common in igl
Eigen::MatrixXd crosses(const Eigen::MatrixXd& V, const Eigen::Vector3d v) {
assert(V.cols() == 3);
Eigen::MatrixXd R;
R.resizeLike(V);
for (int j = 0; j < V.rows(); ++j) {
// ideally once again we would like to do this:
// R.row(j) = cross_t(V.row(j),v);
// but we know better so we can try to do a Map
R.row(j) =
cross_t(Eigen::Map<const Eigen::Vector3d>{V.row(j).data()}, v);
// this actually doesn't work because each entry of V.row(j) lies
// V.rows() apart because Eigen::MatrixXd is ColMajor by default instead
// we would have to declare the stride explicitly via
R.row(j) = cross_t(
Eigen::Map<const Eigen::Vector3d, 0, Eigen::InnerStride<>>{
V.row(j).data(), Eigen::InnerStride<>{V.rows()}},
v);
// Eigen::Ref therefore simplifies this process by computing these
// strides internally
R.row(j) = cross_t(Eigen::Ref<const Eigen::Vector3d>{V.row(j)}, v);
}
return R;
}
// so why is this nice for ABIs?
// imagine we want to expose a super fancy cross product but not expose the
// implementation (templated functions generally force the developer to leak
// implementation details to the public
Eigen::Vector3d cross_r(Eigen::Ref<const Eigen::Vector3d> x,
Eigen::Ref<const Eigen::Vector3d> y);
// in some separate file
//=================
Eigen::Vector3d cross_r(Eigen::Ref<const Eigen::Vector3d> x,
Eigen::Ref<const Eigen::Vector3d> y) {
return x.cross(y);
}
//=================
// this allows for us to call an untemplated cross_r without any worries
// we don't even need to have access to how cross_r is implemented
Eigen::VectorXd cross2(const Eigen::VectorXd& x, const Eigen::VectorXd& y) {
return cross_r(x, y);
}
// beware though, i'm pretty sure Ref can be coerced to create copies,
// something like this might cause it to
Eigen::VectorXf cross2(const Eigen::VectorXf& x, const Eigen::VectorXf& y) {
return cross_r(x.cast<double>(), y.cast<double>()).cast<float>();
}
@physicsmonk
Copy link

Hello my friend, I'm also studying the Ref stuff. So I see you used Ref inside function cross, and according to the main Eigen developer G. Gael (you can see this link: https://stackoverflow.com/questions/21132538/correct-usage-of-the-eigenref-class), when Ref take a const fixed-size matrix type, it has to store a matrix of this type just in case the passed object is not compatible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment