Skip to content

Commit 399ea9c

Browse files
Skip redundant kinematics in inner-term forward sweep
The per-column forward sweep in ComputeABASecondOrderDerivatives was recomputing q-dependent quantities (jdata via jmodel.calc, liMi, oMi, J_cols, and the leaf body inertia in world frame) on every one of the 3*nv iterations, even though they only depend on q and are already populated by the preceding computeABADerivatives and ComputeRNEASecondOrderDerivatives calls. Restrict the per-column forward visitor to the acceleration-dependent work only: propagate data.oa[i] with zero gravity at the root, write ddJ_cols = oa x J_cols, reset data.oYcrb[i] to the cached leaf value (which the backward sweep then composites in place), and compute data.of[i] = oYcrb[i] * oa. The leaf body inertia is cached once in oYcrb_leaf before the loop, since the backward sweep mutates data.oYcrb on each per-column iteration. For humanoidRandom (nv=32) this drops ComputeABASecondOrderDerivatives median from ~2765us to ~2524us (~9 percent faster). Refs: stack-of-tasks#2891
1 parent 09a177f commit 399ea9c

1 file changed

Lines changed: 39 additions & 31 deletions

File tree

include/pinocchio/src/algorithm/aba-second-order-derivatives.hxx

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,41 @@
1616
namespace pinocchio
1717
{
1818

19-
// Forward sweep used by ComputeABASecondOrderDerivatives to set up the
20-
// articulated-body-style inner-product recursion: forward kinematics, the
21-
// body spatial inertia oYcrb[i] (composited into a CRBA-style composite
22-
// inertia by the backward sweep), and the spatial force oY * oa
23-
// corresponding to the input acceleration `a` propagated through the chain.
24-
// Gravity is deliberately zeroed at the base so the result of the
25-
// subsequent backward sweep is (dM/dq)*a only -- no gravity contribution.
19+
// Forward sweep used by ComputeABASecondOrderDerivatives to propagate the
20+
// input acceleration `a` through the chain. Only acceleration-dependent
21+
// quantities are recomputed per call:
22+
// - data.oa[i]: spatial acceleration with zero gravity at the root, so
23+
// the backward sweep yields (dM/dq)*a only (no gravity contribution).
24+
// - ddJ_cols = oa x J_cols.
25+
// - data.of[i] = oYcrb_leaf[i] * oa, the spatial force at body i.
26+
// - data.oYcrb[i] is reset to oYcrb_leaf[i], since the backward sweep
27+
// composites oYcrb in place and we need the leaf values at the start
28+
// of each per-column iteration.
29+
// q-dependent quantities (data.oMi, data.liMi, data.J, data.joints[i] with
30+
// a valid jdata.S(), and the world-frame leaf inertia oYcrb_leaf cached by
31+
// the caller) are assumed to already be set by the prior computeABADerivatives
32+
// and ComputeRNEASecondOrderDerivatives calls, so we deliberately skip
33+
// jmodel.calc and the liMi/oMi/J_cols updates that a standard ABA forward
34+
// sweep would perform.
2635
template<
2736
typename Scalar,
2837
int Options,
2938
template<typename, int> class JointCollectionTpl,
30-
typename ConfigVectorType,
3139
typename InputAccType>
3240
struct ComputeABASecondOrderDerivativesInnerForwardStep
3341
: public fusion::JointUnaryVisitorBase<ComputeABASecondOrderDerivativesInnerForwardStep<
3442
Scalar,
3543
Options,
3644
JointCollectionTpl,
37-
ConfigVectorType,
3845
InputAccType>>
3946
{
4047
typedef ModelTpl<Scalar, Options, JointCollectionTpl> Model;
4148
typedef DataTpl<Scalar, Options, JointCollectionTpl> Data;
49+
typedef typename Data::Inertia Inertia;
50+
typedef std::vector<Inertia> InertiaVector;
4251

4352
typedef boost::fusion::
44-
vector<const Model &, Data &, const ConfigVectorType &, const InputAccType &>
53+
vector<const Model &, Data &, const InputAccType &, const InertiaVector &>
4554
ArgsType;
4655

4756
template<typename JointModel>
@@ -50,44 +59,32 @@ namespace pinocchio
5059
JointDataBase<typename JointModel::JointDataDerived> & jdata,
5160
const Model & model,
5261
Data & data,
53-
const Eigen::MatrixBase<ConfigVectorType> & q,
54-
const Eigen::MatrixBase<InputAccType> & a)
62+
const Eigen::MatrixBase<InputAccType> & a,
63+
const InertiaVector & oYcrb_leaf)
5564
{
5665
typedef typename Model::JointIndex JointIndex;
5766
typedef typename Data::Motion Motion;
58-
typedef typename Data::Inertia Inertia;
5967

6068
const JointIndex i = jmodel.id();
6169
const JointIndex parent = model.parents[i];
6270
Motion & oa = data.oa[i];
6371

64-
jmodel.calc(jdata.derived(), q.derived());
65-
data.liMi[i] = model.jointPlacements[i] * jdata.M();
66-
6772
if (parent > 0)
68-
{
69-
data.oMi[i] = data.oMi[parent] * data.liMi[i];
7073
oa = data.oa[parent];
71-
}
7274
else
73-
{
74-
data.oMi[i] = data.liMi[i];
7575
oa.setZero(); // zero gravity: result is (dM/dq)*a, not (dM/dq)*a + dh/dq
76-
}
7776

7877
typedef
7978
typename SizeDepType<JointModel::NV>::template ColsReturn<typename Data::Matrix6x>::Type
8079
ColsBlock;
8180
ColsBlock J_cols = jmodel.jointCols(data.J);
8281
ColsBlock ddJ_cols = jmodel.jointCols(data.ddJ);
8382

84-
J_cols.noalias() = data.oMi[i].act(jdata.S());
8583
motionSet::motionAction(oa, J_cols, ddJ_cols);
8684
oa += data.oMi[i].act(jdata.S() * jmodel.jointVelocitySelector(a));
8785

88-
Inertia & oY = data.oYcrb[i];
89-
oY = data.oMi[i].act(model.inertias[i]);
90-
data.of[i] = oY * oa;
86+
data.oYcrb[i] = oYcrb_leaf[i];
87+
data.of[i] = data.oYcrb[i] * oa;
9188
}
9289
};
9390

@@ -216,9 +213,7 @@ namespace pinocchio
216213
// 1) First-order ABA derivatives: fills data.ddq, data.ddq_dq, data.ddq_dv
217214
// and (upper triangle of) data.Minv.
218215
computeABADerivatives(model, data, q.derived(), v.derived(), tau.derived());
219-
// Symmetrize Minv: computeABADerivatives only fills the upper triangle, but
220-
// the dense GEMM in step 4 and the inner-term sweep for the dtaudq block
221-
// (step 3, fill_inner(data.Minv, prodqdd)) both require the full matrix.
216+
// Symmetrize Minv: computeABADerivatives only fills the upper triangle
222217
data.Minv.template triangularView<Eigen::StrictlyLower>() =
223218
data.Minv.transpose().template triangularView<Eigen::StrictlyLower>();
224219

@@ -230,13 +225,26 @@ namespace pinocchio
230225

231226
// 3) Inner-Term: for each column w of (ddq_dq, ddq_dv, Minv), compute
232227
// (dM/dq) * column and store as page w of the corresponding tensor.
228+
//
229+
// The per-column forward sweep below is the acceleration-only pass; it
230+
// reuses the q-dependent kinematics (data.oMi, data.J, data.joints[i])
231+
// already populated by computeABADerivatives and SO RNEA, and the leaf
232+
// body inertias cached in oYcrb_leaf right below.
233233
typedef ComputeABASecondOrderDerivativesInnerForwardStep<
234-
Scalar, Options, JointCollectionTpl, ConfigVectorType, VectorXs>
234+
Scalar, Options, JointCollectionTpl, VectorXs>
235235
InnerPass1;
236236
typedef ComputeABASecondOrderDerivativesInnerBackwardStep<
237237
Scalar, Options, JointCollectionTpl, Matrix6x, MatrixXs>
238238
InnerPass2;
239239

240+
// Cache the world-frame leaf body inertia once. SO RNEA leaves
241+
// data.oYcrb in its composited state, and the inner-term backward sweep
242+
// mutates data.oYcrb again per call, so we restore the leaf values at
243+
// the start of each per-column forward pass.
244+
typename InnerPass1::InertiaVector oYcrb_leaf((std::size_t)model.njoints);
245+
for (JointIndex i = 1; i < (JointIndex)model.njoints; ++i)
246+
oYcrb_leaf[i] = data.oMi[i].act(model.inertias[i]);
247+
240248
// TODO: preallocate these scratch buffers (three nv-cubed tensors, two
241249
// 6-by-nv matrices, one nv-by-nv matrix, plus the two nv-by-4*nv^2
242250
// matrices in step 4) as Data fields to avoid the per-call allocations
@@ -254,7 +262,7 @@ namespace pinocchio
254262
for (JointIndex i = 1; i < (JointIndex)model.njoints; ++i)
255263
InnerPass1::run(
256264
model.joints[i], data.joints[i],
257-
typename InnerPass1::ArgsType(model, data, q.derived(), a_input));
265+
typename InnerPass1::ArgsType(model, data, a_input, oYcrb_leaf));
258266

259267
Ftmp1.setZero();
260268
Ftmp3.setZero();

0 commit comments

Comments
 (0)