Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test vector transport and its inverse with dIntegrateTransport #2273

Merged
merged 7 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added

- Python unittest for `contactInverseDynamics` function ([#2263](https://github.com/stack-of-tasks/pinocchio/pull/2263))
- c++ and Python unittest for `dIntegrateTransport` to check vector transport and its inverse ([#2273](https://github.com/stack-of-tasks/pinocchio/pull/2273))

### Removed

Expand Down
68 changes: 68 additions & 0 deletions unittest/liegroups.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,49 @@ struct LieGroup_JintegrateJdifference
}
};

struct LieGroup_dIntegrateTransport
{
template<typename T>
void operator()(const T) const
{
typedef typename T::ConfigVector_t ConfigVector_t;
typedef typename T::TangentVector_t TangentVector_t;
typedef typename T::JacobianMatrix_t JacobianMatrix_t;

PINOCCHIO_COMPILER_DIAGNOSTIC_PUSH
PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_MAYBE_UNINITIALIZED
T lg;
BOOST_TEST_MESSAGE(lg.name());
ConfigVector_t qa, qb(lg.nq());
qa = lg.random();
TangentVector_t v(lg.nv()), tvec_at_qb(lg.nv()), tvec_at_qa(lg.nv()), tvec_at_qa_r(lg.nv());
v.setRandom();
lg.integrate(qa, v, qb);

// transport random tangent vector from q1 to q0
tvec_at_qb.setRandom();
lg.dIntegrateTransport(qa, v, tvec_at_qb, tvec_at_qa, ARG0);

// test reverse direction
TangentVector_t v_r = -v; // reverse path
ConfigVector_t qa_r = lg.integrate(qb, v_r);
lg.dIntegrateTransport(qa_r, v_r, tvec_at_qa, tvec_at_qa_r, ARG0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be dIntegrateTransport(qb, v_r, tvec_at_qa, ...)?

Since we check that qa is equal to qa_r below, this line amounts to integrating $v_r$ from $qa_r$, where we will end... Somewhere else than $q_a$ or $q_b$ 😅

Meanwhile this line would fully make sense to me if we start from $q_b$: then $q_b \oplus v_r$ would land on $q_{ar} = q_a$, where we take tvec_at_qa, and transport it back to $q_b$, which is why we have the equality between tvec_at_qb and tvec_at_qa_r below. (But then, tvec_at_qa_r lies in the tangent space at $q_b$, not $q_{ar}$! 😛)

@fabinsch I understand the test passed so I must have missed something. If you can double check at some point that would be 👌

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meanwhile I fully agree with the Python variant.


BOOST_CHECK_SMALL((qa - qa_r).norm(), 1e-6); // recover init point on manifold
BOOST_CHECK_SMALL((tvec_at_qb - tvec_at_qa_r).norm(), 1e-6);

// same test for matrix
JacobianMatrix_t J_at_qa(lg.nv(), lg.nv());
J_at_qa.setRandom();
JacobianMatrix_t J_at_qb(lg.nv(), lg.nv());
lg.dIntegrateTransport(qa, v, J_at_qa, J_at_qb, ARG0);
JacobianMatrix_t J_at_qa_r(lg.nv(), lg.nv());
lg.dIntegrateTransport(qa_r, v_r, J_at_qb, J_at_qa_r, ARG0);

BOOST_CHECK_SMALL((J_at_qa - J_at_qa_r).norm(), 1e-6);
}
};

struct LieGroup_JintegrateCoeffWise
{
template<typename T>
Expand Down Expand Up @@ -566,6 +609,31 @@ BOOST_AUTO_TEST_CASE(Jdifference)
boost::mpl::for_each<Types>(LieGroup_Jdifference());
}

BOOST_AUTO_TEST_CASE(dIntegrateTransport)
{
typedef double Scalar;
enum
{
Options = 0
};

typedef boost::mpl::vector<
VectorSpaceOperationTpl<1, Scalar, Options>, VectorSpaceOperationTpl<2, Scalar, Options>,
SpecialOrthogonalOperationTpl<2, Scalar, Options>,
SpecialOrthogonalOperationTpl<3, Scalar, Options>,
SpecialEuclideanOperationTpl<2, Scalar, Options>,
SpecialEuclideanOperationTpl<3, Scalar, Options>,
CartesianProductOperation<
VectorSpaceOperationTpl<2, Scalar, Options>,
SpecialOrthogonalOperationTpl<2, Scalar, Options>>,
CartesianProductOperation<
VectorSpaceOperationTpl<3, Scalar, Options>,
SpecialOrthogonalOperationTpl<3, Scalar, Options>>>
Types;
for (int i = 0; i < 20; ++i)
boost::mpl::for_each<Types>(LieGroup_dIntegrateTransport());
}

BOOST_AUTO_TEST_CASE(Jintegrate)
{
typedef double Scalar;
Expand Down
34 changes: 34 additions & 0 deletions unittest/python/bindings_liegroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,40 @@ def test_dIntegrateTransport(self):
Jout1_ref = Jint.dot(J0)
self.assertApprox(Jout1, Jout1_ref)

def test_dIntegrateTransport_inverse(self):
for lg in [
pin.liegroups.R3(),
pin.liegroups.SO3(),
pin.liegroups.SO2(),
pin.liegroups.SE3(),
pin.liegroups.SE2(),
pin.liegroups.R3() * pin.liegroups.SO3(),
]:
q0 = lg.random()
v = np.random.rand(lg.nv)
q1 = lg.integrate(q0, v)

# transport random tangent vector from q1 to q0
tvec_at_q1 = np.random.rand(lg.nv)
tvec_at_q0 = lg.dIntegrateTransport(q0, v, tvec_at_q1, pin.ARG0)

# test reverse direction
v_r = -v.copy() # reverse path
q0_r = lg.integrate(q1, v_r)

self.assertApprox(q0, q0_r) # recover init point on manifold

tvec_at_q1_r = lg.dIntegrateTransport(q1, v_r, tvec_at_q0, pin.ARG0)

self.assertApprox(tvec_at_q1, tvec_at_q1_r)

# same test for matrix
J_at_q1 = np.random.rand(lg.nv, lg.nv)
J_at_q0 = lg.dIntegrateTransport(q0, v, J_at_q1, pin.ARG0)
self.assertApprox(
J_at_q1, lg.dIntegrateTransport(q1, v_r, J_at_q0, pin.ARG0)
)


if __name__ == "__main__":
unittest.main()
Loading