Skip to content

Commit 7eb8231

Browse files
yuvaltassacopybara-github
authored andcommitted
Represent only the lower triangle in Newton solver's reduced dof-dof matrix.
PiperOrigin-RevId: 712488529 Change-Id: Iad91c72654376539791d7856765a0d0ac9088251
1 parent ee6f483 commit 7eb8231

11 files changed

+112
-76
lines changed

src/engine/engine_core_constraint.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2176,12 +2176,12 @@ void mj_projectConstraint(const mjModel* m, mjData* d) {
21762176

21772177
// AR = JM2 * JM2'
21782178
mju_sqrMatTDSparseInit(d->efc_AR_rownnz, d->efc_AR_rowadr, nefc, rownnzT,
2179-
rowadrT, colindT, rownnz, rowadr, colind, rowsuper, d);
2179+
rowadrT, colindT, rownnz, rowadr, colind, rowsuper, d, /*flg_upper=*/1);
21802180

21812181
mju_sqrMatTDSparse(d->efc_AR, JM2T, JM2, NULL, nv, nefc,
21822182
d->efc_AR_rownnz, d->efc_AR_rowadr, d->efc_AR_colind,
21832183
rownnzT, rowadrT, colindT, NULL,
2184-
rownnz, rowadr, colind, rowsuper, d);
2184+
rownnz, rowadr, colind, rowsuper, d, /*flg_upper=*/1);
21852185

21862186
// add R to diagonal of AR
21872187
for (int i=0; i < nefc; i++) {

src/engine/engine_io.c

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -943,8 +943,11 @@ static void makeDofDofSparse(const mjModel* m, mjData* d,
943943
// process below diagonal unless reduced and dof is simple
944944
if (!reduced || !m->dof_simplenum[i]) {
945945
while ((j = m->dof_parentid[j]) >= 0) {
946+
// both reduced and non-reduced have lower triangle
946947
rownnz[i]++;
947-
rownnz[j]++;
948+
949+
// only non-reduced has upper triangle
950+
if (!reduced) rownnz[j]++;
948951
}
949952
}
950953
}
@@ -969,8 +972,11 @@ static void makeDofDofSparse(const mjModel* m, mjData* d,
969972
remaining[i]--;
970973
colind[rowadr[i] + remaining[i]] = j;
971974

972-
remaining[j]--;
973-
colind[rowadr[j] + remaining[j]] = i;
975+
// only non-reduced has upper triangle
976+
if (!reduced) {
977+
remaining[j]--;
978+
colind[rowadr[j] + remaining[j]] = i;
979+
}
974980
}
975981
}
976982
}
@@ -1152,8 +1158,11 @@ static void copyM2Sparse(const mjModel* m, mjData* d, int* dst, const int* src,
11521158
remaining[i]--;
11531159
dst[rowadr[i] + remaining[i]] = src[adr];
11541160

1155-
remaining[j]--;
1156-
dst[rowadr[j] + remaining[j]] = src[adr];
1161+
// only non-reduced has upper triangle
1162+
if (!reduced) {
1163+
remaining[j]--;
1164+
dst[rowadr[j] + remaining[j]] = src[adr];
1165+
}
11571166

11581167
adr++;
11591168
}
@@ -1172,7 +1181,7 @@ static void copyM2Sparse(const mjModel* m, mjData* d, int* dst, const int* src,
11721181

11731182

11741183

1175-
// integer valued dst[M] = src[D lower], handle different sparsity representations
1184+
// integer valued dst[M] = src[D lower]
11761185
static void copyD2MSparse(const mjModel* m, const mjData* d, int* dst, const int* src) {
11771186
int nv = m->nv;
11781187

@@ -1197,7 +1206,7 @@ static void copyD2MSparse(const mjModel* m, const mjData* d, int* dst, const int
11971206

11981207

11991208
// construct index mappings between M <-> D and M -> C
1200-
static void makeDmap(const mjModel* m, mjData* d) {
1209+
static void makeDofDofmap(const mjModel* m, mjData* d) {
12011210
int nM = m->nM, nC = m->nC, nD = m->nD;
12021211
mj_markStack(d);
12031212

@@ -1955,7 +1964,7 @@ static void _resetData(const mjModel* m, mjData* d, unsigned char debug_value) {
19551964

19561965
// make C
19571966
makeDofDofSparse(m, d, d->C_rownnz, d->C_rowadr, d->C_diag, d->C_colind, /*reduced=*/1);
1958-
makeDmap(m, d);
1967+
makeDofDofmap(m, d);
19591968
}
19601969

19611970
// restore pluginstate and plugindata

src/engine/engine_solver.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ static void MakeHessian(const mjModel* m, mjData* d, mjCGContext* ctx) {
14031403
mju_sqrMatTDSparseInit(ctx->H_rownnz, ctx->H_rowadr, nv,
14041404
d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind,
14051405
d->efc_JT_rownnz, d->efc_JT_rowadr, d->efc_JT_colind, d->efc_JT_rowsuper,
1406-
d);
1406+
d, /*flg_upper=*/0);
14071407

14081408
// add nC to Hessian total nonzeros (unavoidable overcounting since H_colind is still unknown)
14091409
ctx->nH = m->nC + ctx->H_rowadr[nv - 1] + ctx->H_rownnz[nv - 1];
@@ -1424,14 +1424,24 @@ static void MakeHessian(const mjModel* m, mjData* d, mjCGContext* ctx) {
14241424
ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind,
14251425
d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind, NULL,
14261426
d->efc_JT_rownnz, d->efc_JT_rowadr, d->efc_JT_colind, d->efc_JT_rowsuper,
1427-
d);
1427+
d, /*flg_upper=*/0);
14281428

14291429
// add mass matrix: H = J'*D*J + C
14301430
mj_addMSparse(m, d, ctx->H, ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind,
14311431
ctx->C, d->C_rownnz, d->C_rowadr, d->C_colind);
14321432

1433+
// transiently compute H'; mju_cholFactorNNZ is memory-contiguous in upper triangle layout
1434+
mj_markStack(d);
1435+
int* HT_rownnz = mjSTACKALLOC(d, nv, int);
1436+
int* HT_rowadr = mjSTACKALLOC(d, nv, int);
1437+
int* HT_colind = mjSTACKALLOC(d, ctx->nH, int);
1438+
mju_transposeSparse(NULL, NULL, nv, nv,
1439+
HT_rownnz, HT_rowadr, HT_colind,
1440+
ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind);
1441+
14331442
// count total and row non-zeros of reverse-Cholesky factor L
1434-
ctx->nL = mju_cholFactorNNZ(ctx->L_rownnz, ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind, nv, d);
1443+
ctx->nL = mju_cholFactorNNZ(ctx->L_rownnz, HT_rownnz, HT_rowadr, HT_colind, nv, d);
1444+
mj_freeStack(d);
14351445

14361446
// compute L row adresses: rowadr = cumsum(rownnz)
14371447
ctx->L_rowadr[0] = 0;
@@ -1508,7 +1518,7 @@ static void FactorizeHessian(const mjModel* m, mjData* d, mjCGContext* ctx,
15081518
ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind,
15091519
d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind, NULL,
15101520
d->efc_JT_rownnz, d->efc_JT_rowadr, d->efc_JT_colind, d->efc_JT_rowsuper,
1511-
d);
1521+
d, /*flg_upper=*/0);
15121522

15131523
// add mass matrix: H = J'*D*J + C
15141524
mj_addMSparse(m, d, ctx->H, ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind,

src/engine/engine_util_sparse.c

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ void mju_superSparse(int nr, int* rowsuper,
628628
void mju_sqrMatTDSparseInit(int* res_rownnz, int* res_rowadr, int nr,
629629
const int* rownnz, const int* rowadr, const int* colind,
630630
const int* rownnzT, const int* rowadrT, const int* colindT,
631-
const int* rowsuperT, mjData* d) {
631+
const int* rowsuperT, mjData* d, int flg_upper) {
632632
mj_markStack(d);
633633
int* chain = mjSTACKALLOC(d, 2*nr, int);
634634
int nchain = 0;
@@ -640,8 +640,10 @@ void mju_sqrMatTDSparseInit(int* res_rownnz, int* res_rowadr, int nr,
640640
res_rownnz[r] = res_rownnz[r - 1];
641641

642642
// fill in upper triangle
643-
for (int j=0; j < nchain; j++) {
644-
res_rownnz[res_colind[j]]++;
643+
if (flg_upper) {
644+
for (int j=0; j < nchain; j++) {
645+
res_rownnz[res_colind[j]]++;
646+
}
645647
}
646648

647649
// update chain with diagonal
@@ -691,15 +693,17 @@ void mju_sqrMatTDSparseInit(int* res_rownnz, int* res_rowadr, int nr,
691693
res_colind = chain + inew;
692694

693695
// update upper triangle
694-
int nchain_end = nchain;
696+
if (flg_upper) {
697+
int nchain_end = nchain;
695698

696-
// avoid double counting.
697-
if (nchain > 0 && res_colind[nchain-1] == r) {
698-
nchain_end = nchain - 1;
699-
}
699+
// avoid double counting
700+
if (nchain > 0 && res_colind[nchain-1] == r) {
701+
nchain_end = nchain - 1;
702+
}
700703

701-
for (int j=0; j < nchain_end; j++) {
702-
res_rownnz[res_colind[j]]++;
704+
for (int j=0; j < nchain_end; j++) {
705+
res_rownnz[res_colind[j]]++;
706+
}
703707
}
704708
}
705709
}
@@ -731,7 +735,7 @@ void mju_sqrMatTDSparse(mjtNum* res, const mjtNum* mat, const mjtNum* matT,
731735
const int* colind, const int* rowsuper,
732736
const int* rownnzT, const int* rowadrT,
733737
const int* colindT, const int* rowsuperT,
734-
mjData* d) {
738+
mjData* d, int flg_upper) {
735739
// allocate space for accumulation buffer and matT
736740
mj_markStack(d);
737741

@@ -846,13 +850,15 @@ void mju_sqrMatTDSparse(mjtNum* res, const mjtNum* mat, const mjtNum* matT,
846850

847851

848852
// fill upper triangle
849-
for (int i=0; i < nc; i++) {
850-
int start = res_rowadr[i];
851-
int end = start + res_rownnz[i] - 1;
852-
for (int j=start; j < end; j++) {
853-
int adr = res_rowadr[res_colind[j]] + res_rownnz[res_colind[j]]++;
854-
res[adr] = res[j];
855-
res_colind[adr] = i;
853+
if (flg_upper) {
854+
for (int i=0; i < nc; i++) {
855+
int start = res_rowadr[i];
856+
int end = start + res_rownnz[i] - 1;
857+
for (int j=start; j < end; j++) {
858+
int adr = res_rowadr[res_colind[j]] + res_rownnz[res_colind[j]]++;
859+
res[adr] = res[j];
860+
res_colind[adr] = i;
861+
}
856862
}
857863
}
858864

src/engine/engine_util_sparse.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ MJAPI void mju_sqrMatTDSparse(mjtNum* res, const mjtNum* mat, const mjtNum* matT
9696
const int* colind, const int* rowsuper,
9797
const int* rownnzT, const int* rowadrT,
9898
const int* colindT, const int* rowsuperT,
99-
mjData* d);
99+
mjData* d, int flg_upper);
100100

101101
// precount res_rownnz and precompute res_rowadr for mju_sqrMatTDSparse
102102
MJAPI void mju_sqrMatTDSparseInit(int* res_rownnz, int* res_rowadr, int nr,
103103
const int* rownnz, const int* rowadr, const int* colind,
104104
const int* rownnzT, const int* rowadrT, const int* colindT,
105-
const int* rowsuperT, mjData* d);
105+
const int* rowsuperT, mjData* d, int flg_upper);
106106

107107
// precompute res_rowadr for mju_sqrMatTDSparse using uncompressed memory
108108
MJAPI void mju_sqrMatTDUncompressedInit(int* res_rowadr, int nc);

src/user/user_model.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2495,7 +2495,7 @@ void mjCModel::CopyTree(mjModel* m) {
24952495
}
24962496
}
24972497
}
2498-
m->nC = nC = 2 * nOD + nv;
2498+
m->nC = nC = nOD + nv;
24992499
}
25002500

25012501
// copy plugin data

test/benchmark/engine_util_sparse_benchmark_test.cc

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void ABSL_ATTRIBUTE_NOINLINE mju_sqrMatTDSparse_baseline(
4343
int nr, int nc, int* res_rownnz, int* res_rowadr, int* res_colind,
4444
const int* rownnz, const int* rowadr, const int* colind,
4545
const int* rowsuper, const int* rownnzT, const int* rowadrT,
46-
const int* colindT, const int* rowsuperT, mjData* d) {
46+
const int* colindT, const int* rowsuperT, mjData* d, int unused) {
4747
mj_markStack(d);
4848
int* chain = mj_stackAllocInt(d, 2 * nc);
4949
mjtNum* buffer = mj_stackAllocNum(d, nc);
@@ -453,7 +453,8 @@ static void BM_combineSparse(benchmark::State& state, CombineFuncPtr func) {
453453
d->efc_J_rownnz, d->efc_J_rowadr,
454454
d->efc_J_colind, d->efc_J_rowsuper,
455455
d->efc_JT_rownnz, d->efc_JT_rowadr,
456-
d->efc_JT_colind, d->efc_JT_rowsuper, d);
456+
d->efc_JT_colind, d->efc_JT_rowsuper, d,
457+
/*flg_upper=*/1);
457458

458459
// compute H = M + J'*D*J
459460
mj_addM(m, d, H, rownnz, rowadr, colind);
@@ -578,7 +579,7 @@ static void BM_sqrMatTDSparse(benchmark::State& state, SqrMatTDFuncPtr func) {
578579
func(H, d->efc_J, d->efc_JT, D, d->nefc, m->nv, rownnz, rowadr, colind,
579580
d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind, NULL,
580581
d->efc_JT_rownnz, d->efc_JT_rowadr, d->efc_JT_colind,
581-
d->efc_JT_rowsuper, d);
582+
d->efc_JT_rowsuper, d, /*flg_upper=*/1);
582583
}
583584
} else {
584585
for (auto s : state) {
@@ -587,10 +588,11 @@ static void BM_sqrMatTDSparse(benchmark::State& state, SqrMatTDFuncPtr func) {
587588
d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind);
588589

589590
// compute H = J'*D*J, uncompressed layout
590-
mju_sqrMatTDSparse_baseline(H, d->efc_J, d->efc_JT, D, d->nefc, m->nv, rownnz, rowadr, colind,
591-
d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind, d->efc_J_rowsuper,
592-
d->efc_JT_rownnz, d->efc_JT_rowadr, d->efc_JT_colind,
593-
d->efc_JT_rowsuper, d);
591+
mju_sqrMatTDSparse_baseline(
592+
H, d->efc_J, d->efc_JT, D, d->nefc, m->nv, rownnz, rowadr, colind,
593+
d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind, d->efc_J_rowsuper,
594+
d->efc_JT_rownnz, d->efc_JT_rowadr, d->efc_JT_colind,
595+
d->efc_JT_rowsuper, d, /*unused=*/0);
594596
}
595597
}
596598

test/engine/engine_core_smooth_test.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,8 +481,12 @@ TEST_F(CoreSmoothTest, SolveLDs) {
481481
vector<mjtNum> LDdense2(nv*nv);
482482
mj_fullM(m, LDdense2.data(), d->qLD);
483483

484-
// expect dense matrices to match exactly
485-
for (int i=0; i < nv*nv; i++) EXPECT_EQ(LDdense[i], LDdense2[i]);
484+
// expect lower triangles to match exactly
485+
for (int i=0; i < nv; i++) {
486+
for (int j=0; j < i; j++) {
487+
EXPECT_EQ(LDdense[i*nv+j], LDdense2[i*nv+j]);
488+
}
489+
}
486490

487491
// compare LD and LDs vector solve
488492
vector<mjtNum> vec(nv);

test/engine/engine_solver_test.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ static const char* const kIlslandEfcPath =
6161
// compare accelerations produced by CG solver with and without islands
6262
TEST_F(SolverTest, IslandsEquivalent) {
6363
const std::string xml_path = GetTestDataFilePath(kIlslandEfcPath);
64-
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0);
65-
ASSERT_THAT(model, NotNull());
64+
char error[1024];
65+
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, error, sizeof(error));
66+
ASSERT_THAT(model, NotNull()) << error;
6667
model->opt.solver = mjSOL_CG; // use CG solver
6768
model->opt.tolerance = 0; // set tolerance to 0
6869
model->opt.enableflags &= ~mjENBL_ISLAND; // disable islands

test/engine/engine_support_test.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,8 +725,12 @@ TEST_F(InertiaTest, DenseSameAsSparse) {
725725
// dense addM
726726
mj_addM(m, d, dst_dense.data(), nullptr, nullptr, nullptr);
727727

728-
// dense comparison, should be same matrix
729-
EXPECT_THAT(dst_dense, ElementsAreArray(dst_sparse));
728+
// dense comparison, lower triangle should match
729+
for (int i=0; i < nv; i++) {
730+
for (int j=0; j < i; j++) {
731+
EXPECT_EQ(dst_dense[i*nv+j], dst_sparse[i*nv+j]);
732+
}
733+
}
730734

731735
// clean up
732736
mj_deleteData(d);

0 commit comments

Comments
 (0)