892d889793
Reformulate the cost difference calculation (`ellipticCostDif`) to use mathematically equivalent formulas that avoid subtracting large, nearly equal values (cancellation errors) in single precision at high normal forces. This is a C port of Alain's formulation in MJWarp: https://github.com/google-deepmind/mujoco_warp/pull/1512 Also adds an integration test (`EllipticLineSearchPrecisionDiagnostics`) that reproduces the precision issue under large normal forces in the sliding regime, and asserts that the solver does not produce large negative improvements in either precision. This test failed before the change. PiperOrigin-RevId: 946137815 Change-Id: Ia8fc1c4823b5fee770140c8989b9465737d22ad7
2508 lines
74 KiB
C
2508 lines
74 KiB
C
// Copyright 2021 DeepMind Technologies Limited
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "engine/engine_solver.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include <mujoco/mjdata.h>
|
|
#include <mujoco/mjmacro.h>
|
|
#include <mujoco/mjmodel.h>
|
|
#include <mujoco/mjsan.h> // IWYU pragma: keep
|
|
#include "engine/engine_core_constraint.h"
|
|
#include "engine/engine_core_smooth.h"
|
|
#include "engine/engine_core_util.h"
|
|
#include "engine/engine_memory.h"
|
|
#include "engine/engine_macro.h"
|
|
#include "engine/engine_util_blas.h"
|
|
#include "engine/engine_util_errmem.h"
|
|
#include "engine/engine_util_misc.h"
|
|
#include "engine/engine_util_solve.h"
|
|
#include "engine/engine_util_sparse.h"
|
|
|
|
|
|
//---------------------------------- utility functions ---------------------------------------------
|
|
|
|
// save solver statistics
|
|
static void saveStats(const mjModel* m, mjData* d, int island, int iter,
|
|
mjtNum improvement, mjtNum gradient, mjtNum lineslope,
|
|
int nactive, int nchange, int neval, int nupdate) {
|
|
// if island out of range, return
|
|
if (island >= mjNISLAND) {
|
|
return;
|
|
}
|
|
|
|
// if no islands, use first island
|
|
island = mjMAX(0, island);
|
|
|
|
// if iter out of range, return
|
|
if (iter >= mjNSOLVER) {
|
|
return;
|
|
}
|
|
|
|
// get mjSolverStat pointer
|
|
mjSolverStat* stat = d->solver + island*mjNSOLVER + iter;
|
|
|
|
// save stats
|
|
stat->improvement = improvement;
|
|
stat->gradient = gradient;
|
|
stat->lineslope = lineslope;
|
|
stat->nactive = nactive;
|
|
stat->nchange = nchange;
|
|
stat->neval = neval;
|
|
stat->nupdate = nupdate;
|
|
}
|
|
|
|
|
|
// finalize dual solver: map to joint space
|
|
static void dualFinish(const mjModel* m, mjData* d) {
|
|
// map constraint force to joint space
|
|
mj_mulJacTVec(m, d, d->qfrc_constraint, d->efc_force);
|
|
|
|
// compute constrained acceleration in joint space
|
|
mj_solveM(m, d, d->qacc, d->qfrc_constraint, 1);
|
|
mju_addTo(d->qacc, d->qacc_smooth, m->nv);
|
|
}
|
|
|
|
|
|
// PGS: map efc_force to joint space
|
|
void mj_dualFinish(const mjModel* m, mjData* d) {
|
|
dualFinish(m, d);
|
|
}
|
|
|
|
|
|
// compute 1/diag(AR)
|
|
// res[c] = 1 / AR[efclist[c], efclist[c]] for c = 0..nefc-1
|
|
// efclist is NULL for monolithic (sequential) iteration
|
|
static void ARdiaginv(const mjModel* m, const mjData* d, mjtNum* res,
|
|
int nefc, const int* efclist, int flg_subR) {
|
|
const mjtNum *AR = d->efc_AR;
|
|
const mjtNum *R = d->efc_R;
|
|
|
|
// sparse
|
|
if (mj_isSparse(m)) {
|
|
const int *rowadr = d->efc_AR_rowadr;
|
|
const int *rownnz = d->efc_AR_rownnz;
|
|
const int *colind = d->efc_AR_colind;
|
|
|
|
for (int c=0; c < nefc; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
int nnz = rownnz[i];
|
|
for (int j=0; j < nnz; j++) {
|
|
int adr = rowadr[i] + j;
|
|
if (i == colind[adr]) {
|
|
res[c] = 1 / (flg_subR ? mju_max(mjMINVAL, AR[adr] - R[i]) : AR[adr]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// dense
|
|
else {
|
|
int d_nefc = d->nefc; // global nefc
|
|
for (int c=0; c < nefc; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
int adr = i * (d_nefc + 1);
|
|
res[c] = 1 / (flg_subR ? mju_max(mjMINVAL, AR[adr] - R[i]) : AR[adr]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// extract diagonal block from AR, clamp diag to 1e-10 if flg_subR
|
|
static void extractBlock(const mjModel* m, const mjData* d, mjtNum* Ac,
|
|
int start, int n, int flg_subR) {
|
|
int nefc = d->nefc;
|
|
const mjtNum *AR = d->efc_AR;
|
|
|
|
// sparse
|
|
if (mj_isSparse(m)) {
|
|
const int* rownnz = d->efc_AR_rownnz;
|
|
const int* rowadr = d->efc_AR_rowadr;
|
|
const int* colind = d->efc_AR_colind;
|
|
/*
|
|
// GENERAL CASE
|
|
mju_zero(Ac, n*n);
|
|
for( j=0; j<n; j++ )
|
|
for( k=0; k<rownnz[start+j]; k++ )
|
|
{
|
|
int col = colind[rowadr[start+j]+k];
|
|
if( col>=start && col<start+n )
|
|
Ac[j*n+col-start] = AR[rowadr[start+j]+k];
|
|
}
|
|
*/
|
|
// assume full sub-matrix, find starting k: same for all rows
|
|
int k;
|
|
for (k=0; k < rownnz[start]; k++) {
|
|
if (colind[rowadr[start]+k] == start) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// SHOULD NOT OCCUR
|
|
if (k >= rownnz[start]) {
|
|
mjERROR("internal error");
|
|
}
|
|
|
|
// copy rows
|
|
for (int j=0; j < n; j++) {
|
|
mju_copy(Ac+j*n, AR+rowadr[start+j]+k, n);
|
|
}
|
|
}
|
|
|
|
// dense
|
|
else {
|
|
for (int j=0; j < n; j++) {
|
|
mju_copy(Ac+j*n, AR+start+(start+j)*nefc, n);
|
|
}
|
|
}
|
|
|
|
// subtract R from diagonal, clamp to 1e-10 from below
|
|
if (flg_subR) {
|
|
const mjtNum *R = d->efc_R;
|
|
for (int j=0; j < n; j++) {
|
|
Ac[j*(n+1)] -= R[start+j];
|
|
Ac[j*(n+1)] = mju_max(1e-10, Ac[j*(n+1)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// compute residual for one block
|
|
static void residual(const mjModel* m, const mjData* d, mjtNum* res, int i, int dim, int flg_subR) {
|
|
int nefc = d->nefc;
|
|
|
|
// sparse
|
|
if (mj_isSparse(m)) {
|
|
for (int j=0; j < dim; j++) {
|
|
res[j] = d->efc_b[i+j] + mju_dotSparse(d->efc_AR + d->efc_AR_rowadr[i+j],
|
|
d->efc_force,
|
|
d->efc_AR_rownnz[i+j],
|
|
d->efc_AR_colind + d->efc_AR_rowadr[i+j]);
|
|
}
|
|
}
|
|
|
|
// dense
|
|
else {
|
|
for (int j=0; j < dim; j++) {
|
|
res[j] = d->efc_b[i+j] + mju_dot(d->efc_AR+(i+j)*nefc, d->efc_force, nefc);
|
|
}
|
|
}
|
|
|
|
if (flg_subR) {
|
|
for (int j=0; j < dim; j++) {
|
|
res[j] -= d->efc_R[i+j]*d->efc_force[i+j];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// compute cost change
|
|
static mjtNum costChange(const mjtNum* A, mjtNum* force, const mjtNum* oldforce,
|
|
const mjtNum* res, int dim) {
|
|
mjtNum change;
|
|
|
|
// compute change
|
|
if (dim == 1) {
|
|
mjtNum delta = force[0] - oldforce[0];
|
|
change = 0.5*delta*delta*A[0] + delta*res[0];
|
|
} else {
|
|
mjtNum delta[6];
|
|
mju_sub(delta, force, oldforce, dim);
|
|
change = 0.5*mju_mulVecMatVec(delta, A, delta, dim) + mju_dot(delta, res, dim);
|
|
}
|
|
|
|
// positive change: restore force
|
|
if (change > 1e-10) {
|
|
mju_copy(force, oldforce, dim);
|
|
change = 0;
|
|
}
|
|
|
|
return change;
|
|
}
|
|
|
|
|
|
// PCG32 random number generator state
|
|
typedef struct {
|
|
uint64_t state;
|
|
uint64_t inc;
|
|
} pcg32_state;
|
|
|
|
|
|
// generate next 32-bit pseudorandom integer
|
|
static uint32_t pcg32_next(pcg32_state* rng) {
|
|
uint64_t oldstate = rng->state;
|
|
rng->state = oldstate * 6364136223846793005ULL + (rng->inc | 1);
|
|
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
|
|
uint32_t rot = oldstate >> 59u;
|
|
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
|
|
}
|
|
|
|
|
|
// Fisher-Yates shuffle of integer array
|
|
static void shuffle_int(int* array, int n, pcg32_state* rng) {
|
|
for (int i = n - 1; i > 0; i--) {
|
|
uint32_t j = pcg32_next(rng) % (i + 1);
|
|
int temp = array[i];
|
|
array[i] = array[j];
|
|
array[j] = temp;
|
|
}
|
|
}
|
|
|
|
|
|
// set efc_state to dual constraint state; return nactive
|
|
// iterates over efclist (or sequentially if NULL), classifies by ne/nf ranges
|
|
static int dualState(const mjData* d, int* state,
|
|
int ne, int nf, int nefc, const int* efclist) {
|
|
const mjtNum* force = d->efc_force;
|
|
const mjtNum* floss = d->efc_frictionloss;
|
|
|
|
// equality and friction always active
|
|
int nactive = ne + nf;
|
|
|
|
// equality
|
|
for (int c=0; c < ne; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
state[i] = mjCNSTRSTATE_QUADRATIC;
|
|
}
|
|
|
|
// friction
|
|
for (int c=ne; c < ne+nf; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
if (force[i] <= -floss[i]) {
|
|
state[i] = mjCNSTRSTATE_LINEARPOS; // opposite of primal
|
|
} else if (force[i] >= floss[i]) {
|
|
state[i] = mjCNSTRSTATE_LINEARNEG;
|
|
} else {
|
|
state[i] = mjCNSTRSTATE_QUADRATIC;
|
|
}
|
|
}
|
|
|
|
// limit and contact
|
|
for (int c=ne+nf; c < nefc; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
|
|
// non-negative
|
|
if (d->efc_type[i] != mjCNSTR_CONTACT_ELLIPTIC) {
|
|
if (force[i] <= 0) {
|
|
state[i] = mjCNSTRSTATE_SATISFIED;
|
|
} else {
|
|
state[i] = mjCNSTRSTATE_QUADRATIC;
|
|
nactive++;
|
|
}
|
|
}
|
|
|
|
// elliptic
|
|
else {
|
|
// get contact dimensionality, friction, mu
|
|
mjContact* con = d->contact + d->efc_id[i];
|
|
int dim = con->dim, result = 0;
|
|
mjtNum mu = con->mu, f[6];
|
|
|
|
// f = map force to regular-cone space
|
|
f[0] = force[i]/mu;
|
|
for (int j=1; j < dim; j++) {
|
|
f[j] = force[i+j]/con->friction[j-1];
|
|
}
|
|
|
|
// N = normal, T = norm of tangent vector
|
|
mjtNum N = f[0];
|
|
mjtNum T = mju_norm(f+1, dim-1);
|
|
|
|
// top zone
|
|
if (mu*N >= T) {
|
|
result = mjCNSTRSTATE_SATISFIED;
|
|
}
|
|
|
|
// bottom zone
|
|
else if (N+mu*T <= 0) {
|
|
result = mjCNSTRSTATE_QUADRATIC;
|
|
nactive += dim;
|
|
}
|
|
|
|
// middle zone
|
|
else {
|
|
result = mjCNSTRSTATE_CONE;
|
|
nactive += dim;
|
|
}
|
|
|
|
// replicate state in all cone dimensions
|
|
mju_fillInt(state+i, result, dim);
|
|
|
|
// advance
|
|
c += (dim-1);
|
|
}
|
|
}
|
|
|
|
return nactive;
|
|
}
|
|
|
|
|
|
// update constraint state, return nactive and nchange
|
|
static int dualStateChange(const mjData* d, int* state, int* oldstate,
|
|
int ne, int nf, int nefc,
|
|
const int* efclist, int* nchange) {
|
|
// save old state
|
|
for (int c=0; c < nefc; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
oldstate[c] = state[i];
|
|
}
|
|
|
|
// update state
|
|
int nactive = dualState(d, state, ne, nf, nefc, efclist);
|
|
|
|
// count state changes
|
|
*nchange = 0;
|
|
for (int c=0; c < nefc; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
*nchange += (oldstate[c] != state[i]);
|
|
}
|
|
|
|
return nactive;
|
|
}
|
|
|
|
|
|
// project onto friction ellipsoid, write to force[i+1..i+dim-1]
|
|
// project tangential force onto friction ellipsoid: sum(f_t[j]^2/mu[j]^2) <= f_n^2
|
|
// if feasible is true, only scale down if outside the ellipsoid
|
|
// if feasible is false, always scale to the boundary
|
|
static void projectEllipsoid(mjtNum* friction, mjtNum normal, const mjtNum* mu,
|
|
int dim, int feasible) {
|
|
mjtNum s = 0;
|
|
for (int j=0; j < dim-1; j++) {
|
|
s += friction[j]*friction[j] / (mu[j]*mu[j]);
|
|
}
|
|
|
|
mjtNum normal2 = normal*normal;
|
|
if (!feasible || s > normal2) {
|
|
mjtNum scl = mju_sqrt(normal2 / mju_max(mjMINVAL, s));
|
|
for (int j=0; j < dim-1; j++) {
|
|
friction[j] *= scl;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// solve QCQP and project onto friction ellipsoid, write to force[i+1..i+dim-1]
|
|
static void solveQCQP(mjtNum* force, int i, int dim,
|
|
mjtNum* Ac, mjtNum* bc, const mjtNum* mu) {
|
|
int flg_active;
|
|
mjtNum v[6];
|
|
|
|
// solve
|
|
if (dim == 3) {
|
|
flg_active = mju_QCQP2(v, Ac, bc, mu, force[i]);
|
|
} else if (dim == 4) {
|
|
flg_active = mju_QCQP3(v, Ac, bc, mu, force[i]);
|
|
} else { // dim == 5
|
|
flg_active = mju_QCQP(v, Ac, bc, mu, force[i], dim-1);
|
|
}
|
|
|
|
// on constraint: put v on ellipsoid, in case QCQP is approximate
|
|
if (flg_active) {
|
|
projectEllipsoid(v, force[i], mu, dim, /*feasible=*/0);
|
|
}
|
|
|
|
// assign
|
|
mju_copy(force+i+1, v, dim-1);
|
|
}
|
|
|
|
|
|
// project contact force block onto friction cone (pyramidal or elliptic)
|
|
static void projectCone(mjtNum* force, const mjtNum* mu, int dim, int type) {
|
|
// elliptic cone: project onto friction ellipsoid
|
|
if (type == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
// clamp normal force
|
|
if (force[0] < 0) {
|
|
mju_zero(force, dim);
|
|
} else {
|
|
projectEllipsoid(force+1, force[0], mu, dim, /*feasible=*/1);
|
|
}
|
|
}
|
|
|
|
// pyramidal or scalar: clamp to non-negative
|
|
else {
|
|
if (force[0] < 0) {
|
|
force[0] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// global variable to toggle Nesterov momentum (for benchmarks/tests)
|
|
mjTHREADLOCAL int mj_nesterov_momentum = 1;
|
|
|
|
|
|
//---------------------------- PGS solver ----------------------------------------------------------
|
|
|
|
// core PGS solver: iterates over constraints specified by efclist
|
|
// island: island index for stats (use -1 for monolithic, mapped to 0)
|
|
// ne, nf, nefc: constraint type counts
|
|
// efclist: maps list position c to monolithic efc index (NULL for sequential)
|
|
static void solPGS(const mjModel* m, mjData* d, int island,
|
|
int ne, int nf, int nefc,
|
|
const int* efclist, int maxiter) {
|
|
const mjtNum *floss = d->efc_frictionloss;
|
|
mjtNum *force = d->efc_force;
|
|
mj_markStack(d);
|
|
mjtNum* ARinv = mjSTACKALLOC(d, nefc, mjtNum);
|
|
int* oldstate = mjSTACKALLOC(d, 2*nefc, int);
|
|
int* blockstart = oldstate + nefc;
|
|
|
|
// Nesterov momentum
|
|
mjtBool nesterov = (mj_nesterov_momentum != 0);
|
|
mjtNum* force_prev = NULL;
|
|
mjtNum* force_momentum = NULL;
|
|
if (nesterov) {
|
|
force_prev = mjSTACKALLOC(d, nefc, mjtNum);
|
|
force_momentum = mjSTACKALLOC(d, nefc, mjtNum);
|
|
mju_gather(force_prev, force, efclist, nefc);
|
|
}
|
|
|
|
int island_stat = mjMAX(0, island); // island index for diagnostic stats
|
|
mjtNum scale = 1 / (m->stat.meaninertia * mjMAX(1, m->nv));
|
|
|
|
// precompute inverse diagonal of AR
|
|
ARdiaginv(m, d, ARinv, nefc, efclist, 0);
|
|
|
|
// initial constraint state
|
|
dualState(d, d->efc_state, ne, nf, nefc, efclist);
|
|
|
|
// build block-index array: one entry per constraint block
|
|
int nblocks = 0;
|
|
for (int c=0; c < nefc; ) {
|
|
blockstart[nblocks++] = c;
|
|
int i = efclist ? efclist[c] : c;
|
|
if (d->efc_type[i] == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
c += d->contact[d->efc_id[i]].dim;
|
|
} else {
|
|
c++;
|
|
}
|
|
}
|
|
|
|
// seed PCG32 RNG with a fixed seed
|
|
pcg32_state rng;
|
|
rng.state = 0;
|
|
rng.inc = 1;
|
|
pcg32_next(&rng);
|
|
|
|
// main iteration
|
|
int iter = 0;
|
|
int nesterov_k = 0; // Nesterov counter (resets on adaptive restart)
|
|
while (iter < maxiter) {
|
|
// Nesterov momentum extrapolation
|
|
if (nesterov) {
|
|
mjtNum beta = 0;
|
|
if (iter > 0) {
|
|
beta = (mjtNum)(nesterov_k - 1) / (mjtNum)(nesterov_k + 2);
|
|
}
|
|
|
|
// update with momentum, save pre-extrapolation value
|
|
if (beta > 0) {
|
|
for (int c=0; c < nefc; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
mjtNum f_save = force[i];
|
|
force[i] += beta*(force[i] - force_prev[c]);
|
|
force_prev[c] = f_save;
|
|
}
|
|
|
|
// friction loss: project onto bounds
|
|
for (int c=ne; c < ne+nf; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
force[i] = mju_clip(force[i], -floss[i], floss[i]);
|
|
}
|
|
|
|
// contact force: project onto friction cone
|
|
for (int c=ne+nf; c < nefc; ) {
|
|
int i = efclist ? efclist[c] : c;
|
|
int dim = 1;
|
|
int type = d->efc_type[i];
|
|
const mjtNum* mu = NULL;
|
|
|
|
if (type == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
dim = d->contact[d->efc_id[i]].dim;
|
|
mu = d->contact[d->efc_id[i]].friction;
|
|
}
|
|
|
|
projectCone(force+i, mu, dim, type);
|
|
c += dim;
|
|
}
|
|
}
|
|
|
|
// iter == 0 or beta <= 0 (nesterov_k <= 1): just save current force
|
|
else {
|
|
mju_gather(force_prev, force, efclist, nefc);
|
|
}
|
|
|
|
// save extrapolated point for gradient restart check
|
|
mju_gather(force_momentum, force, efclist, nefc);
|
|
}
|
|
|
|
// clear improvement
|
|
mjtNum improvement = 0;
|
|
|
|
// shuffle constraint visitation order
|
|
shuffle_int(blockstart, nblocks, &rng);
|
|
|
|
// perform one sweep over constraint blocks
|
|
for (int bi=0; bi < nblocks; bi++) {
|
|
int c = blockstart[bi];
|
|
int i = efclist ? efclist[c] : c;
|
|
|
|
// get constraint dimensionality
|
|
int dim;
|
|
if (d->efc_type[i] == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
dim = d->contact[d->efc_id[i]].dim;
|
|
} else {
|
|
dim = 1;
|
|
}
|
|
|
|
// compute residual for this constraint
|
|
mjtNum res[6];
|
|
residual(m, d, res, i, dim, 0);
|
|
|
|
// save old force
|
|
mjtNum oldforce[6];
|
|
mju_copy(oldforce, force+i, dim);
|
|
|
|
// allocate AR submatrix, required later for costChage
|
|
mjtNum Athis[36];
|
|
|
|
// simple constraint
|
|
if (d->efc_type[i] != mjCNSTR_CONTACT_ELLIPTIC) {
|
|
// unconstrained minimum
|
|
force[i] -= res[0]*ARinv[c];
|
|
|
|
// impose interval and inequality constraints
|
|
if (c >= ne && c < ne+nf) {
|
|
if (force[i] < -floss[i]) {
|
|
force[i] = -floss[i];
|
|
} else if (force[i] > floss[i]) {
|
|
force[i] = floss[i];
|
|
}
|
|
} else if (c >= ne+nf) {
|
|
if (force[i] < 0) {
|
|
force[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// elliptic cone constraint
|
|
else {
|
|
// get friction
|
|
mjtNum *mu = d->contact[d->efc_id[i]].friction;
|
|
|
|
//-------------------- perform normal or ray update
|
|
|
|
// Athis = AR(this,this)
|
|
extractBlock(m, d, Athis, i, dim, 0);
|
|
|
|
// normal force too small: normal update
|
|
if (force[i] < mjMINVAL) {
|
|
// unconstrained minimum
|
|
force[i] -= res[0]*ARinv[c];
|
|
|
|
// clamp
|
|
if (force[i] < 0) {
|
|
force[i] = 0;
|
|
}
|
|
|
|
// clear friction (just in case)
|
|
mju_zero(force+i+1, dim-1);
|
|
}
|
|
|
|
// ray update
|
|
else {
|
|
// v = ray
|
|
mjtNum v[6];
|
|
mju_copy(v, force+i, dim);
|
|
|
|
// denom = v' * AR(this,this) * v
|
|
mjtNum v1[6];
|
|
mju_mulMatVec(v1, Athis, v, dim, dim);
|
|
mjtNum denom = mju_dot(v, v1, dim);
|
|
|
|
// avoid division by 0
|
|
if (denom >= mjMINVAL) {
|
|
// x = v' * res / denom
|
|
mjtNum x = -mju_dot(v, res, dim) / denom;
|
|
|
|
// make sure normal is non-negative
|
|
if (force[i]+x*v[0] < 0) {
|
|
x = -v[0]/force[i];
|
|
}
|
|
|
|
// add x*v to f
|
|
for (int j=0; j < dim; j++) {
|
|
force[i+j] += x*v[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------- perform friction update, keep normal fixed
|
|
|
|
// Ac = AR-submatrix; bc = b-subvector + Ac,rest * f_rest
|
|
mjtNum bc[5], Ac[25];
|
|
mju_copy(bc, res+1, dim-1);
|
|
for (int j=0; j < dim-1; j++) {
|
|
mju_copy(Ac+j*(dim-1), Athis+(j+1)*dim+1, dim-1);
|
|
bc[j] -= mju_dot(Ac+j*(dim-1), oldforce+1, dim-1);
|
|
bc[j] += Athis[(j+1)*dim]*(force[i]-oldforce[0]);
|
|
}
|
|
|
|
// guard for f_normal==0
|
|
if (force[i] < mjMINVAL) {
|
|
mju_zero(force+i+1, dim-1);
|
|
}
|
|
|
|
// QCQP
|
|
else {
|
|
solveQCQP(force, i, dim, Ac, bc, mu);
|
|
}
|
|
}
|
|
|
|
// accumulate improvement
|
|
if (dim == 1) {
|
|
Athis[0] = 1/ARinv[c];
|
|
}
|
|
improvement -= costChange(Athis, force+i, oldforce, res, dim);
|
|
}
|
|
|
|
// update constraint state
|
|
int nchange;
|
|
int nactive = dualStateChange(d, d->efc_state, oldstate, ne, nf, nefc, efclist, &nchange);
|
|
|
|
// scale improvement, save stats
|
|
improvement *= scale;
|
|
saveStats(m, d, island_stat, iter, improvement, 0, 0, nactive, nchange, 0, 0);
|
|
|
|
// Nesterov gradient restart (O'Donoghue-Candès): reset when correction opposes extrapolation
|
|
if (nesterov) {
|
|
mjtBool restart = false;
|
|
if (iter > 0) {
|
|
mjtNum dot_corr_extr = 0;
|
|
for (int c=0; c < nefc; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
mjtNum correction = force[i] - force_momentum[c];
|
|
mjtNum extrapolation = force_momentum[c] - force_prev[c];
|
|
dot_corr_extr += correction * extrapolation;
|
|
}
|
|
restart = (dot_corr_extr < 0);
|
|
}
|
|
|
|
if (restart) {
|
|
nesterov_k = 0;
|
|
} else {
|
|
nesterov_k++;
|
|
}
|
|
}
|
|
|
|
// increment iteration count
|
|
iter++;
|
|
|
|
// terminate
|
|
if (improvement < m->opt.tolerance) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// finalize statistics
|
|
if (island_stat < mjNISLAND) {
|
|
// update solver iterations
|
|
d->solver_niter[island_stat] += iter;
|
|
|
|
// set nnz
|
|
if (mj_isSparse(m)) {
|
|
d->solver_nnz[island_stat] = 0;
|
|
for (int c=0; c < nefc; c++) {
|
|
d->solver_nnz[island_stat] += d->efc_AR_rownnz[efclist ? efclist[c] : c];
|
|
}
|
|
} else {
|
|
d->solver_nnz[island_stat] = nefc*nefc;
|
|
}
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
}
|
|
|
|
|
|
// PGS entry point (monolithic, no dualFinish — caller handles it)
|
|
void mj_solPGS(const mjModel* m, mjData* d, int maxiter) {
|
|
solPGS(m, d, /*island=*/-1, d->ne, d->nf, d->nefc, /*efclist=*/NULL, maxiter);
|
|
}
|
|
|
|
|
|
// PGS entry point (one island)
|
|
void mj_solPGS_island(const mjModel* m, mjData* d, int island, int maxiter) {
|
|
int ne = d->island_ne[island];
|
|
int nf = d->island_nf[island];
|
|
int nefc = d->island_nefc[island];
|
|
int iefcadr = d->island_iefcadr[island];
|
|
|
|
solPGS(m, d, island, ne, nf, nefc, d->map_iefc2efc + iefcadr, maxiter);
|
|
}
|
|
|
|
|
|
//---------------------------- NoSlip solver -------------------------------------------------------
|
|
|
|
// core NoSlip solver: iterates over constraints specified by efclist
|
|
// island: island index for stats (use -1 for monolithic, mapped to 0)
|
|
// ne, nf, nefc: constraint type counts
|
|
// efclist: maps list position c to monolithic efc index (NULL for sequential)
|
|
static void solNoSlip(const mjModel* m, mjData* d, int island,
|
|
int ne, int nf, int nefc,
|
|
const int* efclist, int maxiter) {
|
|
int dim, iter = 0;
|
|
const mjtNum *floss = d->efc_frictionloss;
|
|
mjtNum *force = d->efc_force;
|
|
mjtNum *mu, improvement;
|
|
mjtNum Ac[25], bc[5], res[5], oldforce[5], delta[5], mid, y, K0, K1;
|
|
mjContact* con;
|
|
mj_markStack(d);
|
|
mjtNum* ARinv = mjSTACKALLOC(d, nefc, mjtNum);
|
|
int* oldstate = mjSTACKALLOC(d, nefc, int);
|
|
|
|
int island_stat = mjMAX(0, island);
|
|
mjtNum scale = 1 / (m->stat.meaninertia * mjMAX(1, m->nv));
|
|
|
|
// precompute inverse diagonal of A
|
|
ARdiaginv(m, d, ARinv, nefc, efclist, 1);
|
|
|
|
// initial constraint state
|
|
dualState(d, d->efc_state, ne, nf, nefc, efclist);
|
|
|
|
// main iteration
|
|
while (iter < maxiter) {
|
|
// clear improvement
|
|
improvement = 0;
|
|
|
|
// correct for cost change at iter 0
|
|
if (iter == 0) {
|
|
for (int c=0; c < nefc; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
improvement += 0.5*force[i]*force[i]*d->efc_R[i];
|
|
}
|
|
}
|
|
|
|
// perform one sweep: dry friction
|
|
for (int c=ne; c < ne+nf; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
|
|
// compute residual, save old
|
|
residual(m, d, res, i, 1, 1);
|
|
oldforce[0] = force[i];
|
|
|
|
// unconstrained minimum
|
|
force[i] -= res[0]*ARinv[c];
|
|
|
|
// impose interval constraints
|
|
if (force[i] < -floss[i]) {
|
|
force[i] = -floss[i];
|
|
} else if (force[i] > floss[i]) {
|
|
force[i] = floss[i];
|
|
}
|
|
|
|
// add to improvement
|
|
delta[0] = force[i] - oldforce[0];
|
|
improvement -= 0.5*delta[0]*delta[0]/ARinv[c] + delta[0]*res[0];
|
|
}
|
|
|
|
// perform one sweep: contact friction
|
|
for (int c=ne+nf; c < nefc; c++) {
|
|
int i = efclist ? efclist[c] : c;
|
|
|
|
// pyramidal contact
|
|
if (d->efc_type[i] == mjCNSTR_CONTACT_PYRAMIDAL) {
|
|
// get contact info
|
|
con = d->contact + d->efc_id[i];
|
|
dim = con->dim;
|
|
mu = con->friction;
|
|
|
|
// loop over pairs of opposing pyramid edges
|
|
for (int j=i; j < i+2*(dim-1); j+=2) {
|
|
// compute residual, save old
|
|
residual(m, d, res, j, 2, 1);
|
|
mju_copy(oldforce, force+j, 2);
|
|
|
|
// Ac = AR-submatirx
|
|
extractBlock(m, d, Ac, j, 2, 1);
|
|
|
|
// bc = b-subvector + Ac,rest * f_rest
|
|
mju_copy(bc, res, 2);
|
|
for (int k=0; k < 2; k++) {
|
|
bc[k] -= mju_dot(Ac+k*2, oldforce, 2);
|
|
}
|
|
|
|
// f0 = mid+y, f1 = mid-y
|
|
mid = 0.5*(force[j]+force[j+1]);
|
|
y = 0.5*(force[j]-force[j+1]);
|
|
|
|
// K1 = A00 + A11 - 2*A01, K0 = mid*A00 - mid*A11 + b0 - b1
|
|
K1 = Ac[0] + Ac[3] - Ac[1] - Ac[2];
|
|
K0 = mid*(Ac[0] - Ac[3]) + bc[0] - bc[1];
|
|
|
|
// guard against Ac==0
|
|
if (K1 < mjMINVAL) {
|
|
force[j] = force[j+1] = mid;
|
|
}
|
|
|
|
// otherwise minimize over y \in [-mid, mid]
|
|
else {
|
|
// unconstrained minimum
|
|
y = -K0/K1;
|
|
|
|
// clamp and assign
|
|
if (y < -mid) {
|
|
force[j] = 0;
|
|
force[j+1] = 2*mid;
|
|
} else if (y > mid) {
|
|
force[j] = 2*mid;
|
|
force[j+1] = 0;
|
|
} else {
|
|
force[j] = mid+y;
|
|
force[j+1] = mid-y;
|
|
}
|
|
}
|
|
|
|
// accumulate improvement
|
|
improvement -= costChange(Ac, force+j, oldforce, res, 2);
|
|
}
|
|
|
|
// skip the rest of this contact
|
|
c += 2*(dim-1)-1;
|
|
}
|
|
|
|
// elliptic contact
|
|
else if (d->efc_type[i] == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
// get contact info
|
|
con = d->contact + d->efc_id[i];
|
|
dim = con->dim;
|
|
mu = con->friction;
|
|
|
|
// compute residual, save old
|
|
residual(m, d, res, i+1, dim-1, 1);
|
|
mju_copy(oldforce, force+i+1, dim-1);
|
|
|
|
// Ac = AR-submatrix
|
|
extractBlock(m, d, Ac, i+1, dim-1, 1);
|
|
|
|
// bc = b-subvector + Ac,rest * f_rest
|
|
mju_copy(bc, res, dim-1);
|
|
for (int j=0; j < dim-1; j++) {
|
|
bc[j] -= mju_dot(Ac+j*(dim-1), oldforce, dim-1);
|
|
}
|
|
|
|
// guard for f_normal==0
|
|
if (force[i] < mjMINVAL) {
|
|
mju_zero(force+i+1, dim-1);
|
|
}
|
|
|
|
// QCQP
|
|
else {
|
|
solveQCQP(force, i, dim, Ac, bc, mu);
|
|
}
|
|
|
|
// accumulate improvement
|
|
improvement -= costChange(Ac, force+i+1, oldforce, res, dim-1);
|
|
|
|
// skip the rest of this contact
|
|
c += (dim-1);
|
|
}
|
|
}
|
|
|
|
// update constraint state
|
|
int nchange;
|
|
int nactive = dualStateChange(d, d->efc_state, oldstate, ne, nf, nefc, efclist, &nchange);
|
|
|
|
// scale improvement, save stats
|
|
improvement *= scale;
|
|
|
|
// save noslip stats after all the entries from regular solver
|
|
if (island_stat < mjNISLAND) {
|
|
int stats_iter = iter + d->solver_niter[island_stat];
|
|
saveStats(m, d, island_stat, stats_iter, improvement, 0, 0, nactive, nchange, 0, 0);
|
|
}
|
|
|
|
// increment iteration count
|
|
iter++;
|
|
|
|
// terminate
|
|
if (improvement < m->opt.noslip_tolerance) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update solver iterations
|
|
if (island_stat < mjNISLAND) {
|
|
d->solver_niter[island_stat] += iter;
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
}
|
|
|
|
|
|
// NoSlip entry point (monolithic, no dualFinish — caller handles it)
|
|
void mj_solNoSlip(const mjModel* m, mjData* d, int maxiter) {
|
|
solNoSlip(m, d, /*island=*/-1, d->ne, d->nf, d->nefc, /*efclist=*/NULL, maxiter);
|
|
}
|
|
|
|
|
|
// NoSlip entry point (one island)
|
|
void mj_solNoSlip_island(const mjModel* m, mjData* d, int island, int maxiter) {
|
|
int ne = d->island_ne[island];
|
|
int nf = d->island_nf[island];
|
|
int nefc = d->island_nefc[island];
|
|
int iefcadr = d->island_iefcadr[island];
|
|
|
|
solNoSlip(m, d, island, ne, nf, nefc, d->map_iefc2efc + iefcadr, maxiter);
|
|
}
|
|
|
|
|
|
//------------------------- Primal solvers ---------------------------------------------------------
|
|
|
|
// Primal context
|
|
typedef struct {
|
|
int is_sparse; // 1: sparse, 0: dense
|
|
int is_elliptic; // 1: elliptic, 0: pyramidal
|
|
int island; // current island index, -1 if monolithic
|
|
|
|
// sizes
|
|
int nv; // number of dofs
|
|
int ne; // number of equalities
|
|
int nf; // number of friction constraints
|
|
int nefc; // number of all constraints
|
|
int nJ; // number of nonzeros in Jacobian
|
|
|
|
// contact array
|
|
mjContact* contact;
|
|
|
|
// dof arrays
|
|
const mjtNum* qfrc_smooth;
|
|
const mjtNum* qacc_smooth;
|
|
mjtNum* qfrc_constraint;
|
|
mjtNum* qacc;
|
|
|
|
// inertia
|
|
int* M_rownnz;
|
|
int* M_rowadr;
|
|
int* M_colind;
|
|
mjtNum* M;
|
|
mjtNum* qLD;
|
|
mjtNum* qLDiagInv;
|
|
|
|
// efc arrays
|
|
const mjtNum* efc_D;
|
|
const mjtNum* efc_R;
|
|
const mjtNum* efc_frictionloss;
|
|
const mjtNum* efc_aref;
|
|
const int* efc_id;
|
|
const int* efc_type;
|
|
mjtNum* efc_force;
|
|
int* efc_state;
|
|
|
|
// Jacobians
|
|
int* J_rownnz;
|
|
int* J_rowadr;
|
|
int* J_rowsuper;
|
|
int* J_colind;
|
|
mjtNum* J;
|
|
int* JT_rownnz;
|
|
int* JT_rowadr;
|
|
int* JT_rowsuper;
|
|
int* JT_colind;
|
|
mjtNum* JT;
|
|
|
|
// common arrays (PrimalAllocate)
|
|
mjtNum* Jaref; // Jac*qacc - aref (nefc x 1)
|
|
mjtNum* Jv; // Jac*search (nefc x 1)
|
|
mjtNum* Ma; // M*qacc (nv x 1)
|
|
mjtNum* Mv; // M*search (nv x 1)
|
|
mjtNum* grad; // gradient of master cost (nv x 1)
|
|
mjtNum* Mgrad; // M\grad or H\grad (nv x 1)
|
|
mjtNum* search; // linesearch vector (nv x 1)
|
|
mjtNum* quad; // quadratic polynomials for constraint costs (nefc x 3)
|
|
int* oldstate; // previous constraint state (nefc x 1)
|
|
|
|
// CG arrays (PrimalAllocate, CG only)
|
|
mjtNum* gradold; // previous gradient (nv x 1)
|
|
mjtNum* Mgradold; // previous preconditioned gradient (nv x 1)
|
|
mjtNum* graddif; // grad - gradold (nv x 1)
|
|
mjtNum* Mgraddif; // M\(grad - gradold) (nv x 1)
|
|
|
|
// Newton arrays, known-size (PrimalAllocate)
|
|
mjtNum* D; // constraint inertia (nefc x 1)
|
|
mjtNum* cholupd; // scratch for rank-1 Cholesky updates (nv x 1)
|
|
mjtNum* LTJ; // L'*J for cone Cholesky updates (6 x nv)
|
|
int* H_rowadr; // Hessian row addresses (nv x 1)
|
|
int* H_rownnz; // Hessian row nonzeros (nv x 1)
|
|
int* HT_rownnz; // Hessian transpose row nonzeros (nv x 1)
|
|
int* HT_rowadr; // Hessian transpose row addresses (nv x 1)
|
|
int* L_rownnz; // Hessian factor row nonzeros (nv x 1)
|
|
int* L_rowadr; // Hessian factor row addresses (nv x 1)
|
|
int* LT_rownnz; // Hessian factor transpose row nonzeros (nv x 1)
|
|
int* LT_rowadr; // Hessian factor transpose row addresses (nv x 1)
|
|
|
|
// Newton arrays, computed-size (MakeHessian)
|
|
int nH; // number of nonzeros in Hessian H
|
|
int* H_colind; // Hessian column indices (nH x 1)
|
|
int* HT_colind; // Hessian transpose column indices (nH x 1)
|
|
mjtNum* H; // Hessian (nH x 1)
|
|
int nL; // number of nonzeros in Cholesky factor L
|
|
int* L_colind; // Cholesky factor column indices (nL x 1)
|
|
int* LT_colind; // Cholesky factor transpose column indices (nL x 1)
|
|
int* LT_map; // CSC-to-CSR index mapping (nL x 1)
|
|
mjtNum* L; // Cholesky factor (nL x 1)
|
|
mjtNum* Lcone; // Cholesky factor with cone contributions (nL x 1)
|
|
|
|
// globals
|
|
mjtNum cost; // constraint + Gauss cost
|
|
mjtNum quadGauss[3]; // quadratic polynomial for Gauss cost
|
|
mjtNum scale; // scaling factor for improvement and gradient
|
|
int nactive; // number of active constraints
|
|
int ncone; // number of contacts in cone state
|
|
int nupdate; // number of Cholesky updates
|
|
|
|
// linesearch diagnostics
|
|
int LSiter; // number of linesearch iterations
|
|
int LSresult; // linesearch result
|
|
mjtNum LSslope; // linesearch slope at solution
|
|
} mjPrimalContext;
|
|
|
|
|
|
// set sizes and pointers to mjData arrays in mjPrimalContext
|
|
static void PrimalPointers(const mjModel* m, const mjData* d, mjPrimalContext* ctx, int island) {
|
|
// clear everything
|
|
memset(ctx, 0, sizeof(mjPrimalContext));
|
|
|
|
// globals
|
|
ctx->is_sparse = mj_isSparse(m);
|
|
ctx->is_elliptic = (m->opt.cone == mjCONE_ELLIPTIC);
|
|
ctx->contact = d->contact;
|
|
ctx->island = island;
|
|
|
|
// set sizes and pointers (monolithic)
|
|
if (island < 0) {
|
|
// sizes
|
|
ctx->nv = m->nv;
|
|
ctx->ne = d->ne;
|
|
ctx->nf = d->nf;
|
|
ctx->nefc = d->nefc;
|
|
ctx->nJ = d->nJ;
|
|
|
|
// dof arrays
|
|
ctx->qfrc_smooth = d->qfrc_smooth;
|
|
ctx->qfrc_constraint = d->qfrc_constraint;
|
|
ctx->qacc_smooth = d->qacc_smooth;
|
|
ctx->qacc = d->qacc;
|
|
|
|
// inertia
|
|
ctx->M_rownnz = m->M_rownnz;
|
|
ctx->M_rowadr = m->M_rowadr;
|
|
ctx->M_colind = m->M_colind;
|
|
ctx->M = d->M;
|
|
ctx->qLD = d->qLD;
|
|
ctx->qLDiagInv = d->qLDiagInv;
|
|
|
|
// efc arrays
|
|
ctx->efc_D = d->efc_D;
|
|
ctx->efc_R = d->efc_R;
|
|
ctx->efc_frictionloss = d->efc_frictionloss;
|
|
ctx->efc_aref = d->efc_aref;
|
|
ctx->efc_id = d->efc_id;
|
|
ctx->efc_type = d->efc_type;
|
|
ctx->efc_force = d->efc_force;
|
|
ctx->efc_state = d->efc_state;
|
|
|
|
// Jacobians
|
|
ctx->J = d->efc_J;
|
|
if (ctx->is_sparse) {
|
|
ctx->J_rownnz = d->efc_J_rownnz;
|
|
ctx->J_rowadr = d->efc_J_rowadr;
|
|
ctx->J_rowsuper = d->efc_J_rowsuper;
|
|
ctx->J_colind = d->efc_J_colind;
|
|
}
|
|
}
|
|
|
|
// set sizes and pointers (per-island)
|
|
else {
|
|
// sizes
|
|
ctx->nv = d->island_nv[island];
|
|
ctx->ne = d->island_ne[island];
|
|
ctx->nf = d->island_nf[island];
|
|
ctx->nefc = d->island_nefc[island];
|
|
|
|
// dof arrays
|
|
int idofadr = d->island_idofadr[island];
|
|
ctx->qfrc_smooth = d->ifrc_smooth + idofadr;
|
|
ctx->qfrc_constraint = d->ifrc_constraint + idofadr;
|
|
ctx->qacc_smooth = d->iacc_smooth + idofadr;
|
|
ctx->qacc = d->iacc + idofadr;
|
|
|
|
// efc arrays
|
|
int iefcadr = d->island_iefcadr[island];
|
|
ctx->efc_D = d->iefc_D + iefcadr;
|
|
ctx->efc_R = d->iefc_R + iefcadr;
|
|
ctx->efc_frictionloss = d->iefc_frictionloss + iefcadr;
|
|
ctx->efc_aref = d->iefc_aref + iefcadr;
|
|
ctx->efc_id = d->iefc_id + iefcadr;
|
|
ctx->efc_type = d->iefc_type + iefcadr;
|
|
ctx->efc_force = d->iefc_force + iefcadr;
|
|
ctx->efc_state = d->iefc_state + iefcadr;
|
|
}
|
|
}
|
|
|
|
|
|
// allocate fixed-size arrays in mjPrimalContext
|
|
// mj_{mark/free}Stack in calling function!
|
|
static void PrimalAllocate(const mjModel* m, mjData* d, mjPrimalContext* ctx, int flg_Newton) {
|
|
// local sizes and flags
|
|
int nv = ctx->nv;
|
|
int nefc = ctx->nefc;
|
|
int is_sparse = ctx->is_sparse;
|
|
int is_elliptic = ctx->is_elliptic;
|
|
int nJ = is_sparse ? d->nJ : 0;
|
|
|
|
// compute island matrix sizes if needed
|
|
int nC = 0;
|
|
if (ctx->island >= 0) {
|
|
// count nC: number of nonzeros in M block of island (always sparse)
|
|
int island = ctx->island;
|
|
int idofadr = d->island_idofadr[island];
|
|
for (int i = 0; i < nv; i++) {
|
|
int dof = d->map_idof2dof[idofadr + i];
|
|
nC += m->M_rownnz[dof];
|
|
}
|
|
|
|
// count nJ: number of nonzeros in J block of island (sparse or dense)
|
|
if (is_sparse) {
|
|
nJ = 0;
|
|
int iefcadr = d->island_iefcadr[island];
|
|
for (int i = 0; i < nefc; i++) {
|
|
int efc = d->map_iefc2efc[iefcadr + i];
|
|
nJ += d->efc_J_rownnz[efc];
|
|
}
|
|
} else {
|
|
nJ = nefc * nv;
|
|
}
|
|
ctx->nJ = nJ;
|
|
}
|
|
|
|
// compute mjtNum block size
|
|
size_t nNum = 5*nefc + 5*nv; // common arrays
|
|
if (is_sparse) nNum += nJ; // JT
|
|
if (flg_Newton) {
|
|
nNum += nefc + nv; // D, cholupd
|
|
if (is_elliptic) nNum += 6*nv; // LTJ
|
|
if (!is_sparse) {
|
|
nNum += nv*nv; // L (dense)
|
|
if (is_elliptic) nNum += nv*nv; // Lcone (dense)
|
|
}
|
|
} else {
|
|
nNum += 4*nv; // CG arrays
|
|
}
|
|
|
|
// add island matrix sizes
|
|
if (ctx->island >= 0) {
|
|
nNum += 2 * nC + nv + nJ; // iM, iLD, iLDiagInv, iefc_J
|
|
}
|
|
|
|
// compute int block size
|
|
size_t nInt = nefc; // oldstate
|
|
if (is_sparse) {
|
|
nInt += 3*nv + nJ; // JT sparse
|
|
if (flg_Newton) nInt += 8*nv; // Newton sparse
|
|
}
|
|
|
|
// add island matrix sizes
|
|
if (ctx->island >= 0) {
|
|
nInt += 2 * nv + nC; // iM_{rownnz, rowadr, colind}
|
|
if (is_sparse) {
|
|
nInt += 3 * nefc + nJ; // iefc_J_{rownnz, rowadr, rowsuper, colind}
|
|
}
|
|
}
|
|
|
|
// allocate mjtNum and int blocks
|
|
mjtNum* numblock = mjSTACKALLOC(d, nNum, mjtNum);
|
|
int* intblock = mjSTACKALLOC(d, nInt, int);
|
|
|
|
// populate island matrices if needed
|
|
if (ctx->island >= 0) {
|
|
int island = ctx->island;
|
|
|
|
int idofadr = d->island_idofadr[island];
|
|
int iefcadr = d->island_iefcadr[island];
|
|
|
|
ctx->M_rownnz = intblock; intblock += nv;
|
|
ctx->M_rowadr = intblock; intblock += nv;
|
|
ctx->M_colind = intblock; intblock += nC;
|
|
|
|
ctx->M = numblock; numblock += nC;
|
|
ctx->qLD = numblock; numblock += nC;
|
|
ctx->qLDiagInv = numblock; numblock += nv;
|
|
|
|
mju_blockSparse(ctx->qLD, ctx->M_rownnz, ctx->M_rowadr, ctx->M_colind,
|
|
d->qLD, m->M_rownnz, m->M_rowadr, m->M_colind,
|
|
nv, d->map_idof2dof + idofadr, d->map_dof2idof,
|
|
d->island_idofadr[island], 0, ctx->M, d->M);
|
|
mju_gather(ctx->qLDiagInv, d->qLDiagInv, d->map_idof2dof + idofadr, nv);
|
|
|
|
ctx->J = numblock; numblock += nJ;
|
|
if (!is_sparse) {
|
|
mju_block(ctx->J, d->efc_J, m->nv, nv, nefc,
|
|
d->map_iefc2efc + iefcadr, d->map_idof2dof + idofadr);
|
|
} else {
|
|
ctx->J_rownnz = intblock; intblock += nefc;
|
|
ctx->J_rowadr = intblock; intblock += nefc;
|
|
ctx->J_rowsuper = intblock; intblock += nefc;
|
|
ctx->J_colind = intblock; intblock += nJ;
|
|
|
|
mju_blockSparse(ctx->J, ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind,
|
|
d->efc_J, d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind,
|
|
nefc, d->map_iefc2efc + iefcadr, d->map_dof2idof,
|
|
d->island_idofadr[island], 0, NULL, NULL);
|
|
|
|
mju_superSparse(nefc, ctx->J_rowsuper, ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind);
|
|
}
|
|
}
|
|
|
|
// carve mjtNum block
|
|
ctx->Jaref = numblock; numblock += nefc;
|
|
ctx->Jv = numblock; numblock += nefc;
|
|
ctx->Ma = numblock; numblock += nv;
|
|
ctx->Mv = numblock; numblock += nv;
|
|
ctx->grad = numblock; numblock += nv;
|
|
ctx->Mgrad = numblock; numblock += nv;
|
|
ctx->search = numblock; numblock += nv;
|
|
ctx->quad = numblock; numblock += 3*nefc;
|
|
if (is_sparse) {
|
|
ctx->JT = numblock; numblock += nJ;
|
|
}
|
|
if (flg_Newton) {
|
|
ctx->D = numblock; numblock += nefc;
|
|
ctx->cholupd = numblock; numblock += nv;
|
|
if (is_elliptic) {
|
|
ctx->LTJ = numblock; numblock += 6*nv;
|
|
}
|
|
if (!is_sparse) {
|
|
ctx->nL = nv*nv;
|
|
ctx->L = numblock; numblock += ctx->nL;
|
|
ctx->Lcone = is_elliptic ? numblock : NULL;
|
|
if (is_elliptic) numblock += ctx->nL;
|
|
}
|
|
} else {
|
|
ctx->gradold = numblock; numblock += nv;
|
|
ctx->Mgradold = numblock; numblock += nv;
|
|
ctx->graddif = numblock; numblock += nv;
|
|
ctx->Mgraddif = numblock; numblock += nv;
|
|
}
|
|
|
|
// carve int block
|
|
ctx->oldstate = intblock; intblock += nefc;
|
|
if (is_sparse) {
|
|
ctx->JT_rownnz = intblock; intblock += nv;
|
|
ctx->JT_rowadr = intblock; intblock += nv;
|
|
ctx->JT_rowsuper = intblock; intblock += nv;
|
|
ctx->JT_colind = intblock; intblock += nJ;
|
|
}
|
|
if (flg_Newton && is_sparse) {
|
|
ctx->H_rowadr = intblock; intblock += nv;
|
|
ctx->H_rownnz = intblock; intblock += nv;
|
|
ctx->HT_rownnz = intblock; intblock += nv;
|
|
ctx->HT_rowadr = intblock; intblock += nv;
|
|
ctx->L_rownnz = intblock; intblock += nv;
|
|
ctx->L_rowadr = intblock; intblock += nv;
|
|
ctx->LT_rownnz = intblock; intblock += nv;
|
|
ctx->LT_rowadr = intblock; intblock += nv;
|
|
}
|
|
|
|
// sparse: compute Jacobian transpose
|
|
if (is_sparse) {
|
|
int offset = ctx->J_rowadr[0];
|
|
mju_transposeSparse(ctx->JT, ctx->J + offset, nefc, nv,
|
|
ctx->JT_rownnz, ctx->JT_rowadr, ctx->JT_colind, ctx->JT_rowsuper,
|
|
ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind + offset);
|
|
}
|
|
}
|
|
|
|
|
|
// update efc_force, qfrc_constraint, cost-related
|
|
static void PrimalUpdateConstraint(mjPrimalContext* ctx, int flg_HessianCone) {
|
|
int nefc = ctx->nefc, nv = ctx->nv;
|
|
|
|
// update constraints
|
|
mj_constraintUpdate_impl(ctx->ne, ctx->nf, ctx->nefc, ctx->efc_D, ctx->efc_R,
|
|
ctx->efc_frictionloss, ctx->Jaref, ctx->efc_type, ctx->efc_id,
|
|
ctx->contact, ctx->efc_state, ctx->efc_force,
|
|
&(ctx->cost), flg_HessianCone);
|
|
|
|
// compute qfrc_constraint (dense or sparse)
|
|
if (!ctx->is_sparse) {
|
|
mju_mulMatTVec(ctx->qfrc_constraint, ctx->J, ctx->efc_force, nefc, nv);
|
|
} else {
|
|
mju_mulMatVecSparse(ctx->qfrc_constraint, ctx->JT, ctx->efc_force, nv,
|
|
ctx->JT_rownnz, ctx->JT_rowadr, ctx->JT_colind, ctx->JT_rowsuper);
|
|
}
|
|
|
|
// count active and cone
|
|
ctx->nactive = 0;
|
|
ctx->ncone = 0;
|
|
for (int i=0; i < nefc; i++) {
|
|
ctx->nactive += (ctx->efc_state[i] != mjCNSTRSTATE_SATISFIED);
|
|
ctx->ncone += (ctx->efc_state[i] == mjCNSTRSTATE_CONE);
|
|
}
|
|
|
|
// add Gauss cost, set in quadratic[0]
|
|
mjtNum Gauss = 0;
|
|
for (int i=0; i < nv; i++) {
|
|
Gauss += 0.5 * (ctx->Ma[i] - ctx->qfrc_smooth[i]) * (ctx->qacc[i] - ctx->qacc_smooth[i]);
|
|
}
|
|
|
|
ctx->quadGauss[0] = Gauss;
|
|
ctx->cost += Gauss;
|
|
}
|
|
|
|
|
|
// update grad, Mgrad
|
|
static void PrimalUpdateGradient(mjPrimalContext* ctx, int flg_Newton) {
|
|
int nv = ctx->nv;
|
|
|
|
// grad = M*qacc - qfrc_smooth - qfrc_constraint
|
|
for (int i=0; i < nv; i++) {
|
|
ctx->grad[i] = ctx->Ma[i] - ctx->qfrc_smooth[i] - ctx->qfrc_constraint[i];
|
|
}
|
|
|
|
// Newton: Mgrad = H \ grad
|
|
if (flg_Newton) {
|
|
if (ctx->is_sparse) {
|
|
mju_cholSolveSparse(ctx->Mgrad, (ctx->ncone ? ctx->Lcone : ctx->L),
|
|
ctx->grad, nv, ctx->L_rownnz, ctx->L_rowadr, ctx->L_colind);
|
|
} else {
|
|
mju_cholSolve(ctx->Mgrad, (ctx->ncone ? ctx->Lcone : ctx->L), ctx->grad, nv);
|
|
}
|
|
}
|
|
|
|
// CG: Mgrad = M \ grad
|
|
else {
|
|
mju_copy(ctx->Mgrad, ctx->grad, nv);
|
|
mj_solveLD(ctx->Mgrad, ctx->qLD, ctx->qLDiagInv, nv, 1,
|
|
ctx->M_rownnz, ctx->M_rowadr, ctx->M_colind, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
// prepare quadratic polynomials and contact cone quantities
|
|
static void PrimalPrepare(mjPrimalContext* ctx) {
|
|
int nv = ctx->nv, nefc = ctx->nefc;
|
|
const mjtNum* v = ctx->search;
|
|
|
|
// Gauss: alpha^2*0.5*v'*M*v + alpha*v'*(Ma-qfrc_smooth) + 0.5*(a-qacc_smooth)'*(Ma-qfrc_smooth)
|
|
// quadGauss[0] already computed in PrimalUpdateConstraint
|
|
ctx->quadGauss[1] = mju_dot(v, ctx->Ma, nv) - mju_dot(ctx->qfrc_smooth, v, nv);
|
|
ctx->quadGauss[2] = 0.5*mju_dot(v, ctx->Mv, nv);
|
|
|
|
// process constraints
|
|
for (int i=0; i < nefc; i++) {
|
|
// pointers to numeric data
|
|
const mjtNum* Jv = ctx->Jv + i;
|
|
const mjtNum* Jaref = ctx->Jaref + i;
|
|
const mjtNum* D = ctx->efc_D + i;
|
|
|
|
// pointer to this quadratic
|
|
mjtNum* quad = ctx->quad + 3*i;
|
|
|
|
// init with scalar quadratic
|
|
mjtNum DJ0 = D[0]*Jaref[0];
|
|
quad[0] = Jaref[0]*DJ0;
|
|
quad[1] = Jv[0]*DJ0;
|
|
quad[2] = Jv[0]*D[0]*Jv[0];
|
|
|
|
// elliptic cone: extra processing
|
|
if (ctx->efc_type[i] == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
// extract contact info
|
|
const mjContact* con = ctx->contact + ctx->efc_id[i];
|
|
int dim = con->dim;
|
|
mjtNum U[6], V[6], UU = 0, UV = 0, VV = 0, mu = con->mu;
|
|
const mjtNum* friction = con->friction;
|
|
|
|
// complete vector quadratic (for bottom zone)
|
|
for (int j=1; j < dim; j++) {
|
|
mjtNum DJj = D[j]*Jaref[j];
|
|
quad[0] += Jaref[j]*DJj;
|
|
quad[1] += Jv[j]*DJj;
|
|
quad[2] += Jv[j]*D[j]*Jv[j];
|
|
}
|
|
|
|
// rescale to make primal cone circular
|
|
U[0] = Jaref[0]*mu;
|
|
V[0] = Jv[0]*mu;
|
|
for (int j=1; j < dim; j++) {
|
|
U[j] = Jaref[j]*friction[j-1];
|
|
V[j] = Jv[j]*friction[j-1];
|
|
}
|
|
|
|
// accumulate sums of squares
|
|
for (int j=1; j < dim; j++) {
|
|
UU += U[j]*U[j];
|
|
UV += U[j]*V[j];
|
|
VV += V[j]*V[j];
|
|
}
|
|
|
|
// store in quad[3-8], using the fact that dim>=3
|
|
quad[3] = U[0];
|
|
quad[4] = V[0];
|
|
quad[5] = UU;
|
|
quad[6] = UV;
|
|
quad[7] = VV;
|
|
quad[8] = D[0] / ((mu*mu) * (1 + (mu*mu)));
|
|
|
|
// advance to next constraint
|
|
i += (dim-1);
|
|
}
|
|
|
|
// apply scaling
|
|
quad[0] *= 0.5;
|
|
quad[2] *= 0.5;
|
|
}
|
|
}
|
|
|
|
|
|
// linesearch evaluation point
|
|
struct _mjPrimalPnt {
|
|
mjtNum alpha;
|
|
mjtNum cost;
|
|
mjtNum deriv[2];
|
|
};
|
|
typedef struct _mjPrimalPnt mjPrimalPnt;
|
|
|
|
|
|
// Huber cost of a single friction constraint at a given point x
|
|
static mjtNum frictionCost(mjtNum x, mjtNum f, mjtNum Rf, mjtNum D) {
|
|
// -bound < x < bound : quadratic
|
|
if (-Rf < x && x < Rf) return 0.5*D*x*x;
|
|
|
|
// x < -bound : linear negative
|
|
else if (x <= -Rf) return f*(-0.5*Rf - x);
|
|
|
|
// bound < x : linear positive
|
|
else return f*(-0.5*Rf + x);
|
|
}
|
|
|
|
|
|
// Huber cost difference of a single friction constraint: cost(x) - cost(start)
|
|
static mjtNum frictionCostDif(mjtNum start, mjtNum x, mjtNum f, mjtNum Rf, mjtNum D) {
|
|
int state_start = (-Rf < start && start < Rf) ? 0 : (start <= -Rf ? -1 : 1);
|
|
int state_x = (-Rf < x && x < Rf) ? 0 : (x <= -Rf ? -1 : 1);
|
|
|
|
// both quadratic
|
|
if (state_start == 0 && state_x == 0) {
|
|
return 0.5*D*(x - start)*(x + start);
|
|
}
|
|
|
|
// both linear negative
|
|
if (state_start == -1 && state_x == -1) {
|
|
return f*(start - x);
|
|
}
|
|
|
|
// both linear positive
|
|
if (state_start == 1 && state_x == 1) {
|
|
return f*(x - start);
|
|
}
|
|
|
|
// otherwise different zones: compute absolute costs and subtract
|
|
return frictionCost(x, f, Rf, D) - frictionCost(start, f, Rf, D);
|
|
}
|
|
|
|
|
|
// compute cost difference of an elliptic cone at a given alpha: cost(alpha) - cost(0)
|
|
static mjtNum ellipticCostDif(const mjtNum* quad, mjtNum alpha, mjtNum mu, mjtNum Dm) {
|
|
mjtNum U0 = quad[3], V0 = quad[4], UU = quad[5];
|
|
mjtNum UV = quad[6], VV = quad[7];
|
|
|
|
// determine zone and cost at alpha=0
|
|
int zone0 = 0;
|
|
mjtNum T0 = 0;
|
|
if (UU <= 0) {
|
|
zone0 = (U0 < 0) ? 2 : 1;
|
|
} else {
|
|
T0 = mju_sqrt(UU);
|
|
if (U0 >= mu*T0) {
|
|
zone0 = 1; // top zone
|
|
} else if (mu*U0 + T0 <= 0) {
|
|
zone0 = 2; // bottom zone
|
|
} else {
|
|
zone0 = 3; // middle zone
|
|
}
|
|
}
|
|
|
|
// determine zone at alpha
|
|
mjtNum N = U0 + alpha*V0;
|
|
mjtNum Tsqr = UU + alpha*(2*UV + alpha*VV);
|
|
int zone_alpha = 0;
|
|
mjtNum T = 0;
|
|
|
|
if (Tsqr <= 0) {
|
|
zone_alpha = (N < 0) ? 2 : 1; // bottom or top zone
|
|
} else {
|
|
T = mju_sqrt(Tsqr);
|
|
if (N >= mu*T) {
|
|
zone_alpha = 1; // top zone
|
|
} else if (mu*N + T <= 0) {
|
|
zone_alpha = 2; // bottom zone
|
|
} else {
|
|
zone_alpha = 3; // middle zone
|
|
}
|
|
}
|
|
|
|
// both top zone
|
|
if (zone0 == 1 && zone_alpha == 1) {
|
|
return 0;
|
|
}
|
|
|
|
// both bottom zone
|
|
if (zone0 == 2 && zone_alpha == 2) {
|
|
return alpha*alpha*quad[2] + alpha*quad[1];
|
|
}
|
|
|
|
// both middle zone: apply rationalized formula to avoid cancellation
|
|
if (zone0 == 3 && zone_alpha == 3) {
|
|
mjtNum Tsqr_delta = alpha*(2*UV + alpha*VV);
|
|
mjtNum T_delta = Tsqr_delta / (T + T0);
|
|
mjtNum r_delta = alpha*V0 - mu*T_delta;
|
|
mjtNum r0 = U0 - mu*T0;
|
|
return 0.5*Dm*r_delta*(2*r0 + r_delta);
|
|
}
|
|
|
|
// CONE -> QUADRATIC (3 -> 2)
|
|
if (zone0 == 3 && zone_alpha == 2) {
|
|
mjtNum dq = alpha*(alpha*quad[2] + quad[1]);
|
|
mjtNum boundary0 = mu*U0 + T0;
|
|
mjtNum gap0 = 0.5*Dm*boundary0*boundary0;
|
|
return dq + gap0;
|
|
}
|
|
|
|
// QUADRATIC -> CONE (2 -> 3)
|
|
if (zone0 == 2 && zone_alpha == 3) {
|
|
mjtNum dq = alpha*(alpha*quad[2] + quad[1]);
|
|
mjtNum boundary = mu*N + T;
|
|
mjtNum gap = 0.5*Dm*boundary*boundary;
|
|
return dq - gap;
|
|
}
|
|
|
|
// SATISFIED -> QUADRATIC (1 -> 2)
|
|
if (zone0 == 1 && zone_alpha == 2) {
|
|
return alpha*alpha*quad[2] + alpha*quad[1] + quad[0];
|
|
}
|
|
|
|
// SATISFIED -> CONE (1 -> 3)
|
|
if (zone0 == 1 && zone_alpha == 3) {
|
|
mjtNum r = N - mu*T;
|
|
return 0.5*Dm*r*r;
|
|
}
|
|
|
|
// CONE -> SATISFIED (3 -> 1)
|
|
if (zone0 == 3 && zone_alpha == 1) {
|
|
mjtNum r0 = U0 - mu*T0;
|
|
return -0.5*Dm*r0*r0;
|
|
}
|
|
|
|
// QUADRATIC -> SATISFIED (2 -> 1)
|
|
if (zone0 == 2 && zone_alpha == 1) {
|
|
return -quad[0];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// evaluate shifted linesearch cost: cost(alpha) - cost(0), and derivatives
|
|
static void PrimalEval(mjPrimalContext* ctx, mjPrimalPnt* p) {
|
|
int ne = ctx->ne, nf = ctx->nf, nefc = ctx->nefc;
|
|
|
|
// clear result
|
|
mjtNum cost = 0, alpha = p->alpha;
|
|
mjtNum deriv[2] = {0, 0};
|
|
|
|
// init quad with Gauss, shifted: drop quadGauss[0]
|
|
mjtNum quadTotal[3] = {0, ctx->quadGauss[1], ctx->quadGauss[2]};
|
|
|
|
// process constraints
|
|
for (int i=0; i < nefc; i++) {
|
|
// equality: shifted quad (skip quad[0])
|
|
if (i < ne) {
|
|
quadTotal[1] += ctx->quad[3*i+1];
|
|
quadTotal[2] += ctx->quad[3*i+2];
|
|
continue;
|
|
}
|
|
|
|
// friction: compute cost(alpha) - cost(0) directly
|
|
if (i < ne + nf) {
|
|
// search point, friction loss, bound (Rf)
|
|
mjtNum start = ctx->Jaref[i];
|
|
mjtNum dir = ctx->Jv[i];
|
|
mjtNum x = start + alpha*dir;
|
|
mjtNum f = ctx->efc_frictionloss[i];
|
|
mjtNum D = ctx->efc_D[i];
|
|
mjtNum Rf = ctx->efc_R[i]*f;
|
|
|
|
// cost delta
|
|
cost += frictionCostDif(start, x, f, Rf, D);
|
|
|
|
// -bound < x < bound : quadratic
|
|
if (-Rf < x && x < Rf) {
|
|
deriv[0] += D*x*dir;
|
|
deriv[1] += D*dir*dir;
|
|
}
|
|
|
|
// x < -bound : linear negative
|
|
else if (x <= -Rf) {
|
|
deriv[0] += -f*dir;
|
|
}
|
|
|
|
// bound < x : linear positive
|
|
else {
|
|
deriv[0] += f*dir;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// limit and contact
|
|
if (ctx->efc_type[i] == mjCNSTR_CONTACT_ELLIPTIC) { // elliptic cone
|
|
// extract contact info
|
|
const mjContact* con = ctx->contact + ctx->efc_id[i];
|
|
mjtNum* quad = ctx->quad + 3*i;
|
|
int dim = con->dim;
|
|
mjtNum mu = con->mu;
|
|
|
|
// unpack quad
|
|
mjtNum U0 = quad[3];
|
|
mjtNum V0 = quad[4];
|
|
mjtNum UU = quad[5];
|
|
mjtNum UV = quad[6];
|
|
mjtNum VV = quad[7];
|
|
mjtNum Dm = quad[8];
|
|
|
|
// shifted cost
|
|
cost += ellipticCostDif(quad, alpha, mu, Dm);
|
|
|
|
// compute N, Tsqr for derivatives
|
|
mjtNum N = U0 + alpha*V0;
|
|
mjtNum Tsqr = UU + alpha*(2*UV + alpha*VV);
|
|
|
|
// no tangential force : top or bottom zone
|
|
if (Tsqr <= 0) {
|
|
// bottom zone: quadratic derivatives
|
|
if (N < 0) {
|
|
deriv[0] += 2*alpha*quad[2] + quad[1];
|
|
deriv[1] += 2*quad[2];
|
|
}
|
|
|
|
// top zone: nothing to do
|
|
}
|
|
|
|
// otherwise regular processing
|
|
else {
|
|
// tangential force
|
|
mjtNum T = mju_sqrt(Tsqr);
|
|
|
|
// N>=mu*T : top zone
|
|
if (N >= mu*T) {
|
|
// nothing to do
|
|
}
|
|
|
|
// mu*N+T<=0 : bottom zone
|
|
else if (mu*N+T <= 0) {
|
|
deriv[0] += 2*alpha*quad[2] + quad[1];
|
|
deriv[1] += 2*quad[2];
|
|
}
|
|
|
|
// otherwise middle zone
|
|
else {
|
|
// derivatives
|
|
mjtNum N1 = V0;
|
|
mjtNum T1 = (UV + alpha*VV)/T;
|
|
mjtNum T2 = VV/T - (UV + alpha*VV)*T1/(T*T);
|
|
deriv[0] += Dm*(N-mu*T)*(N1-mu*T1);
|
|
deriv[1] += Dm*((N1-mu*T1)*(N1-mu*T1) + (N-mu*T)*(-mu*T2));
|
|
}
|
|
}
|
|
|
|
// advance to next constraint
|
|
i += (dim-1);
|
|
} else { // inequality
|
|
// search point
|
|
mjtNum start = ctx->Jaref[i];
|
|
mjtNum x = start + alpha*ctx->Jv[i];
|
|
mjtNum cost0 = start < 0 ? ctx->quad[3*i] : 0;
|
|
|
|
// active
|
|
if (x < 0) {
|
|
// shifted quad: add quad[1], quad[2] and (quad[0] - cost0)
|
|
quadTotal[0] += ctx->quad[3*i] - cost0;
|
|
quadTotal[1] += ctx->quad[3*i+1];
|
|
quadTotal[2] += ctx->quad[3*i+2];
|
|
} else {
|
|
cost -= cost0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add total quadratic (quadTotal[0] contains only shifted residuals)
|
|
cost += alpha*alpha*quadTotal[2] + alpha*quadTotal[1] + quadTotal[0];
|
|
deriv[0] += 2*alpha*quadTotal[2] + quadTotal[1];
|
|
deriv[1] += 2*quadTotal[2];
|
|
|
|
// check for convexity; SHOULD NOT OCCUR
|
|
if (deriv[1] <= 0) {
|
|
mju_warning("Linesearch objective is not convex");
|
|
deriv[1] = mjMINVAL;
|
|
}
|
|
|
|
// assign and count
|
|
p->cost = cost;
|
|
p->deriv[0] = deriv[0];
|
|
p->deriv[1] = deriv[1];
|
|
ctx->LSiter++;
|
|
}
|
|
|
|
|
|
// update bracket point given 3 candidate points
|
|
static int updateBracket(mjPrimalContext* ctx,
|
|
mjPrimalPnt* p, const mjPrimalPnt candidates[3], mjPrimalPnt* pnext) {
|
|
int flag = 0;
|
|
for (int i=0; i < 3; i++) {
|
|
// negative deriv
|
|
if (p->deriv[0] < 0 && candidates[i].deriv[0] < 0 && p->deriv[0] < candidates[i].deriv[0]) {
|
|
*p = candidates[i];
|
|
flag = 1;
|
|
}
|
|
|
|
// positive deriv
|
|
else if (p->deriv[0] > 0 &&
|
|
candidates[i].deriv[0] > 0 &&
|
|
p->deriv[0] > candidates[i].deriv[0]) {
|
|
*p = candidates[i];
|
|
flag = 2;
|
|
}
|
|
}
|
|
|
|
// compute next point if updated
|
|
if (flag) {
|
|
pnext->alpha = p->alpha - p->deriv[0]/p->deriv[1];
|
|
PrimalEval(ctx, pnext);
|
|
}
|
|
|
|
return flag;
|
|
}
|
|
|
|
|
|
// line search
|
|
static mjtNum PrimalSearch(mjPrimalContext* ctx, mjtNum tolerance, mjtNum ls_iterations,
|
|
mjtNum* improvement) {
|
|
int nv = ctx->nv, nefc = ctx->nefc;
|
|
mjPrimalPnt p0, p1, p2, pmid, p1next, p2next;
|
|
|
|
// clear results
|
|
ctx->LSiter = 0;
|
|
ctx->LSresult = 0;
|
|
ctx->LSslope = 1; // means not computed
|
|
*improvement = 0;
|
|
|
|
// save search vector length, check
|
|
mjtNum snorm = mju_norm(ctx->search, nv);
|
|
if (snorm < mjMINVAL) {
|
|
ctx->LSresult = 1; // search vector too small
|
|
return 0;
|
|
}
|
|
|
|
// compute scaled gradtol and slope scaling
|
|
mjtNum gtol = tolerance * snorm / ctx->scale;
|
|
mjtNum slopescl = ctx->scale / snorm;
|
|
|
|
// compute Mv = M * v
|
|
mju_mulSymVecSparse(ctx->Mv, ctx->M, ctx->search, nv,
|
|
ctx->M_rownnz, ctx->M_rowadr, ctx->M_colind);
|
|
|
|
// compute Jv = J * search (dense or sparse)
|
|
if (!ctx->is_sparse) {
|
|
mju_mulMatVec(ctx->Jv, ctx->J, ctx->search, nefc, nv);
|
|
} else {
|
|
mju_mulMatVecSparse(ctx->Jv, ctx->J, ctx->search, nefc,
|
|
ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind, ctx->J_rowsuper);
|
|
}
|
|
|
|
// prepare quadratics and cones
|
|
PrimalPrepare(ctx);
|
|
|
|
// init at alpha = 0, save
|
|
p0.alpha = 0;
|
|
PrimalEval(ctx, &p0);
|
|
|
|
// always attempt one Newton step
|
|
p1.alpha = p0.alpha - p0.deriv[0]/p0.deriv[1];
|
|
PrimalEval(ctx, &p1);
|
|
|
|
// check for initial convergence
|
|
if (mju_abs(p1.deriv[0]) < gtol && (p1.alpha == 0 || p1.cost < 0)) {
|
|
if (p1.alpha == 0) {
|
|
ctx->LSresult = 2; // no improvement, initial convergence
|
|
} else {
|
|
ctx->LSresult = 0; // SUCCESS
|
|
}
|
|
ctx->LSslope = mju_abs(p1.deriv[0])*slopescl;
|
|
*improvement = -p1.cost;
|
|
return p1.alpha;
|
|
}
|
|
|
|
// save direction
|
|
int dir = (p1.deriv[0] < 0 ? +1 : -1);
|
|
|
|
// SANITY CHECKS
|
|
/*
|
|
// descent direction
|
|
if( mju_dot(ctx->grad, ctx->search, m->nv)>=0 )
|
|
printf("NOT A DESCENT: grad %g search %g dot %g\n",
|
|
mju_norm(ctx->grad, m->nv),
|
|
mju_norm(ctx->search, m->nv),
|
|
mju_dot(ctx->grad, ctx->search, m->nv));
|
|
|
|
// 2nd derivative for Newton cone
|
|
if( ctx->flg_Newton && ctx->ncone )
|
|
{
|
|
mjtNum dd = -p0.deriv[0]/p0.deriv[1];
|
|
|
|
if( mju_abs(dd-1)>1e-6 )
|
|
printf("2nd DERIVATIVE FAIL: d0 %g d1 %g alpha %g\n",
|
|
p0.deriv[0], p0.deriv[1], dd);
|
|
}
|
|
|
|
// cost and gradient at 0: full-space vs. linesearch
|
|
mjtNum grd = mju_dot(ctx->grad, ctx->search, m->nv);
|
|
if( mju_abs(p0.cost-ctx->cost)/mjMAX(mjMINVAL,mju_abs(p0.cost+ctx->cost)) > 1e-6 ||
|
|
mju_abs(p0.deriv[0]-grd)/mjMAX(mjMINVAL,mju_abs(p0.deriv[0]+grd)) > 1e-6 )
|
|
{
|
|
printf("LSiter = %d:\n", ctx->LSiter);
|
|
printf("COST: %g %g %g\n",
|
|
p0.cost, ctx->cost,
|
|
mju_abs(p0.cost-ctx->cost)/mjMAX(mjMINVAL,mju_abs(p0.cost+ctx->cost)));
|
|
printf("GRAD: %g %g %g\n",
|
|
p0.deriv[0], grd,
|
|
mju_abs(p0.deriv[0]-grd)/mjMAX(mjMINVAL,mju_abs(p0.deriv[0]+grd)));
|
|
}
|
|
*/
|
|
|
|
// one-sided search
|
|
p2 = p0;
|
|
int p2update = 1;
|
|
while (p1.deriv[0]*dir <= -gtol && ctx->LSiter < ls_iterations) {
|
|
// save current
|
|
p2 = p1;
|
|
p2update = 1;
|
|
|
|
// move to Newton point w.r.t current
|
|
p1.alpha -= p1.deriv[0]/p1.deriv[1];
|
|
PrimalEval(ctx, &p1);
|
|
|
|
// check for convergence
|
|
if (mju_abs(p1.deriv[0]) < gtol && p1.cost < 0) {
|
|
ctx->LSslope = mju_abs(p1.deriv[0])*slopescl;
|
|
*improvement = -p1.cost;
|
|
return p1.alpha; // SUCCESS
|
|
}
|
|
}
|
|
|
|
// check for failure to bracket
|
|
if (ctx->LSiter >= ls_iterations) {
|
|
ctx->LSresult = 3; // could not bracket
|
|
ctx->LSslope = mju_abs(p1.deriv[0])*slopescl;
|
|
*improvement = -p1.cost;
|
|
return p1.alpha;
|
|
}
|
|
|
|
// check for p2 update; SHOULD NOT OCCUR
|
|
if (!p2update) {
|
|
ctx->LSresult = 6; // no p2 update
|
|
ctx->LSslope = mju_abs(p1.deriv[0])*slopescl;
|
|
*improvement = -p1.cost;
|
|
return p1.alpha;
|
|
}
|
|
|
|
// compute next-points for bracket
|
|
p2next = p1;
|
|
p1next.alpha = p1.alpha - p1.deriv[0]/p1.deriv[1];
|
|
PrimalEval(ctx, &p1next);
|
|
|
|
// bracketed search
|
|
while (ctx->LSiter < ls_iterations) {
|
|
// evaluate at midpoint
|
|
pmid.alpha = 0.5*(p1.alpha + p2.alpha);
|
|
PrimalEval(ctx, &pmid);
|
|
|
|
// make list of candidates
|
|
mjPrimalPnt candidates[3] = {p1next, p2next, pmid};
|
|
|
|
// check candidates for convergence
|
|
mjtNum bestcost = 0;
|
|
int bestind = -1;
|
|
for (int i=0; i < 3; i++) {
|
|
if (mju_abs(candidates[i].deriv[0]) < gtol &&
|
|
(bestind == -1 || candidates[i].cost < bestcost)) {
|
|
bestcost = candidates[i].cost;
|
|
bestind = i;
|
|
}
|
|
}
|
|
if (bestind >= 0) {
|
|
ctx->LSslope = mju_abs(candidates[bestind].deriv[0])*slopescl;
|
|
*improvement = -candidates[bestind].cost;
|
|
return candidates[bestind].alpha; // SUCCESS
|
|
}
|
|
|
|
// update brackets
|
|
int b1 = updateBracket(ctx, &p1, candidates, &p1next);
|
|
int b2 = updateBracket(ctx, &p2, candidates, &p2next);
|
|
|
|
// no update possible: numerical accuracy reached, use midpoint
|
|
if (!b1 && !b2) {
|
|
if (pmid.cost < 0) {
|
|
ctx->LSresult = 0; // SUCCESS
|
|
} else {
|
|
ctx->LSresult = 7; // no improvement, could not bracket
|
|
}
|
|
|
|
ctx->LSslope = mju_abs(pmid.deriv[0])*slopescl;
|
|
*improvement = -pmid.cost;
|
|
return pmid.alpha;
|
|
}
|
|
}
|
|
|
|
// choose bracket with best cost
|
|
if (p1.cost <= p2.cost && p1.cost < 0) {
|
|
ctx->LSresult = 4; // improvement but no convergence
|
|
ctx->LSslope = mju_abs(p1.deriv[0])*slopescl;
|
|
*improvement = -p1.cost;
|
|
return p1.alpha;
|
|
} else if (p2.cost <= p1.cost && p2.cost < 0) {
|
|
ctx->LSresult = 4; // improvement but no convergence
|
|
ctx->LSslope = mju_abs(p2.deriv[0])*slopescl;
|
|
*improvement = -p2.cost;
|
|
return p2.alpha;
|
|
} else {
|
|
ctx->LSresult = 5; // no improvement
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
// allocate and compute Hessian given efc_state
|
|
// mj_{mark/free}Stack in caller function!
|
|
static void MakeHessian(mjData* d, mjPrimalContext* ctx) {
|
|
int nv = ctx->nv, nefc = ctx->nefc;
|
|
|
|
// compute constraint inertia
|
|
for (int i=0; i < nefc; i++) {
|
|
ctx->D[i] = ctx->efc_state[i] == mjCNSTRSTATE_QUADRATIC ? ctx->efc_D[i] : 0;
|
|
}
|
|
|
|
// sparse
|
|
if (ctx->is_sparse) {
|
|
// count Hessian nonzeros, initialize rowadr, rownnz
|
|
ctx->nH = mju_sqrMatTDSparseSymbolic(
|
|
ctx->H_rownnz, ctx->H_rowadr, NULL, NULL,
|
|
nefc, nv, ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind,
|
|
ctx->JT_rownnz, ctx->JT_rowadr, ctx->JT_colind, ctx->JT_rowsuper, d);
|
|
|
|
// add M nonzeros to Hessian total (unavoidable overcounting since H_colind is still unknown)
|
|
ctx->nH += ctx->M_rowadr[nv - 1] + ctx->M_rownnz[nv - 1];
|
|
|
|
// nH is known: allocate H, H_colind, HT_colind
|
|
ctx->H = mjSTACKALLOC(d, ctx->nH, mjtNum);
|
|
int* H_intblock = mjSTACKALLOC(d, 2*ctx->nH, int);
|
|
ctx->H_colind = H_intblock;
|
|
ctx->HT_colind = H_intblock + ctx->nH;
|
|
|
|
// shift H row addresses to make room for M
|
|
int shift = 0;
|
|
for (int r = 0; r < nv - 1; r++) {
|
|
shift += ctx->M_rownnz[r];
|
|
ctx->H_rowadr[r + 1] += shift;
|
|
}
|
|
|
|
// compute H = J'*D*J: symbolic phase
|
|
mju_sqrMatTDSparseSymbolic(
|
|
ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind, NULL,
|
|
nefc, nv, ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind,
|
|
ctx->JT_rownnz, ctx->JT_rowadr, ctx->JT_colind, ctx->JT_rowsuper, d);
|
|
|
|
// compute H = J'*D*J: numeric phase
|
|
mju_sqrMatTDSparseNumeric(
|
|
ctx->H, nv, ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind,
|
|
NULL, ctx->J, ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind,
|
|
ctx->JT, ctx->JT_rownnz, ctx->JT_rowadr, ctx->JT_colind,
|
|
ctx->JT_rowsuper, ctx->D, d);
|
|
|
|
// add mass matrix: H = J'*D*J + M
|
|
mju_addToMatSparse(ctx->H, ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind, nv,
|
|
ctx->M, ctx->M_rownnz, ctx->M_rowadr, ctx->M_colind);
|
|
|
|
// compute H' sparse structure (upper triangle, required for symbolic Cholesky)
|
|
mju_transposeSparse(NULL, NULL, nv, nv, ctx->HT_rownnz, ctx->HT_rowadr, ctx->HT_colind, NULL,
|
|
ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind);
|
|
|
|
// count total and row non-zeros of reverse-Cholesky factors L and LT
|
|
ctx->nL = mju_cholFactorSymbolic(NULL, ctx->L_rownnz, ctx->L_rowadr, NULL,
|
|
ctx->LT_rownnz, ctx->LT_rowadr, NULL,
|
|
ctx->HT_rownnz, ctx->HT_rowadr, ctx->HT_colind,
|
|
nv, d);
|
|
|
|
// nL is known: allocate blocks and carve L_colind, LT_colind, LT_map, L, Lcone
|
|
size_t nL_int = 2*ctx->nL + ctx->nL; // L_colind + LT_colind + LT_map
|
|
size_t nL_num = ctx->is_elliptic ? 2*ctx->nL : ctx->nL; // L + Lcone
|
|
int* L_intblock = mjSTACKALLOC(d, nL_int, int);
|
|
mjtNum* L_numblock = mjSTACKALLOC(d, nL_num, mjtNum);
|
|
ctx->L_colind = L_intblock;
|
|
ctx->LT_colind = L_intblock + ctx->nL;
|
|
ctx->LT_map = L_intblock + 2*ctx->nL;
|
|
ctx->L = L_numblock;
|
|
ctx->Lcone = ctx->is_elliptic ? L_numblock + ctx->nL : NULL;
|
|
|
|
// symbolic Cholesky: populate L_colind and LT structures
|
|
mju_cholFactorSymbolic(ctx->L_colind, ctx->L_rownnz, ctx->L_rowadr,
|
|
ctx->LT_colind, ctx->LT_rownnz, ctx->LT_rowadr, ctx->LT_map,
|
|
ctx->HT_rownnz, ctx->HT_rowadr, ctx->HT_colind,
|
|
nv, d);
|
|
}
|
|
|
|
// dense
|
|
else {
|
|
// compute H = M + J'*D*J
|
|
mju_sqrMatTD_impl(ctx->L, ctx->J, ctx->D, nefc, nv, /*flg_upper=*/ 0);
|
|
mju_addToSymSparse(ctx->L, ctx->M, ctx->nv,
|
|
ctx->M_rownnz, ctx->M_rowadr, ctx->M_colind,
|
|
/*flg_upper=*/ 0);
|
|
}
|
|
}
|
|
|
|
|
|
// forward declaration of HessianCone (for readability)
|
|
static void HessianCone(mjData* d, mjPrimalContext* ctx);
|
|
|
|
// factorize Hessian: L = chol(H), maybe (re)compute H given efc_state
|
|
static void FactorizeHessian(mjData* d, mjPrimalContext* ctx, int flg_recompute) {
|
|
int nv = ctx->nv, nefc = ctx->nefc;
|
|
|
|
// maybe compute constraint inertia
|
|
if (flg_recompute) {
|
|
for (int i=0; i < nefc; i++) {
|
|
ctx->D[i] = ctx->efc_state[i] == mjCNSTRSTATE_QUADRATIC ? ctx->efc_D[i] : 0;
|
|
}
|
|
}
|
|
|
|
// sparse
|
|
if (ctx->is_sparse) {
|
|
// maybe compute H = M + J'*D*J
|
|
if (flg_recompute) {
|
|
// compute H = J'*D*J: symbolic phase
|
|
mju_sqrMatTDSparseSymbolic(
|
|
ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind, NULL,
|
|
nefc, nv, ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind,
|
|
ctx->JT_rownnz, ctx->JT_rowadr, ctx->JT_colind, ctx->JT_rowsuper, d);
|
|
|
|
// compute H = J'*D*J: numeric phase
|
|
mju_sqrMatTDSparseNumeric(
|
|
ctx->H, nv, ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind,
|
|
NULL, ctx->J, ctx->J_rownnz, ctx->J_rowadr, ctx->J_colind,
|
|
ctx->JT, ctx->JT_rownnz, ctx->JT_rowadr, ctx->JT_colind,
|
|
ctx->JT_rowsuper, ctx->D, d);
|
|
|
|
// add mass matrix: H = J'*D*J + C
|
|
mju_addToMatSparse(ctx->H, ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind, nv,
|
|
ctx->M, ctx->M_rownnz, ctx->M_rowadr, ctx->M_colind);
|
|
}
|
|
|
|
// numeric sparse factorization: L = chol(H) using pre-computed sparsity pattern
|
|
int rank = mju_cholFactorNumeric(
|
|
ctx->L, nv, mjMINVAL,
|
|
ctx->L_rownnz, ctx->L_rowadr, ctx->L_colind,
|
|
ctx->LT_rownnz, ctx->LT_rowadr, ctx->LT_colind, ctx->LT_map,
|
|
ctx->H, ctx->H_rownnz, ctx->H_rowadr, ctx->H_colind, d);
|
|
|
|
// rank-deficient; SHOULD NOT OCCUR
|
|
if (rank != nv) {
|
|
mjERROR("rank-deficient sparse Hessian");
|
|
}
|
|
}
|
|
|
|
// dense
|
|
else {
|
|
// maybe compute H = M + J'*D*J
|
|
if (flg_recompute) {
|
|
mju_sqrMatTD_impl(ctx->L, ctx->J, ctx->D, nefc, nv, /*flg_upper=*/ 0);
|
|
mju_addToSymSparse(ctx->L, ctx->M, ctx->nv,
|
|
ctx->M_rownnz, ctx->M_rowadr, ctx->M_colind,
|
|
/*flg_upper=*/ 0);
|
|
}
|
|
|
|
// factorize H
|
|
mju_cholFactor(ctx->L, nv, mjMINVAL);
|
|
}
|
|
|
|
// add cones to factor if present
|
|
if (ctx->ncone) {
|
|
HessianCone(d, ctx);
|
|
}
|
|
|
|
// mark full update
|
|
ctx->nupdate = nefc;
|
|
}
|
|
|
|
|
|
// elliptic case: Hcone = H + cone_contributions
|
|
static void HessianCone(mjData* d, mjPrimalContext* ctx) {
|
|
int nv = ctx->nv, nefc = ctx->nefc;
|
|
mjtNum* LTJ = ctx->LTJ;
|
|
mjtNum local[36];
|
|
|
|
// start with Hcone = H
|
|
mju_copy(ctx->Lcone, ctx->L, ctx->nL);
|
|
|
|
// add contributions
|
|
for (int i=0; i < nefc; i++) {
|
|
if (ctx->efc_state[i] == mjCNSTRSTATE_CONE) {
|
|
mjContact* con = ctx->contact + ctx->efc_id[i];
|
|
int dim = con->dim;
|
|
|
|
// Cholesky of local Hessian
|
|
mju_copy(local, con->H, dim*dim);
|
|
mju_cholFactor(local, dim, mjMINVAL);
|
|
|
|
// sparse
|
|
if (ctx->is_sparse) {
|
|
// get nnz for row i (same for all rows in contact)
|
|
const int nnz = ctx->J_rownnz[i];
|
|
|
|
// compute LTJ = L'*J for this contact
|
|
mju_zero(LTJ, dim*nnz);
|
|
for (int r=0; r < dim; r++) {
|
|
for (int c=0; c <= r; c++) {
|
|
mju_addToScl(LTJ+c*nnz, ctx->J+ctx->J_rowadr[i+r], local[r*dim+c], nnz);
|
|
}
|
|
}
|
|
|
|
// update
|
|
for (int r=0; r < dim; r++) {
|
|
mju_cholUpdateSparse(ctx->Lcone, LTJ+r*nnz, nv, 1,
|
|
ctx->L_rownnz, ctx->L_rowadr, ctx->L_colind, nnz,
|
|
ctx->J_colind+ctx->J_rowadr[i+r], d);
|
|
}
|
|
}
|
|
|
|
// dense
|
|
else {
|
|
// compute LTJ = L'*J for this contact row
|
|
mju_zero(LTJ, dim*nv);
|
|
for (int r=0; r < dim; r++) {
|
|
for (int c=0; c <= r; c++) {
|
|
mju_addToScl(LTJ+c*nv, ctx->J+(i+r)*nv, local[r*dim+c], nv);
|
|
}
|
|
}
|
|
|
|
// update
|
|
for (int r=0; r < dim; r++) {
|
|
mju_cholUpdate(ctx->Lcone, LTJ+r*nv, nv, 1);
|
|
}
|
|
}
|
|
|
|
// count updates
|
|
ctx->nupdate += dim;
|
|
|
|
// advance to next constraint
|
|
i += (dim-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// incremental update to Hessian factor due to changes in efc_state
|
|
static void HessianIncremental(mjData* d, mjPrimalContext* ctx, const int* oldstate) {
|
|
int rank, nv = ctx->nv, nefc = ctx->nefc;
|
|
mjtNum* cholupd = ctx->cholupd;
|
|
|
|
// clear update counter
|
|
ctx->nupdate = 0;
|
|
|
|
// update H factorization
|
|
for (int i=0; i < nefc; i++) {
|
|
int flag_update = -1;
|
|
|
|
// add quad
|
|
if (oldstate[i] != mjCNSTRSTATE_QUADRATIC && ctx->efc_state[i] == mjCNSTRSTATE_QUADRATIC) {
|
|
flag_update = 1;
|
|
}
|
|
|
|
// subtract quad
|
|
else if (oldstate[i] == mjCNSTRSTATE_QUADRATIC && ctx->efc_state[i] != mjCNSTRSTATE_QUADRATIC) {
|
|
flag_update = 0;
|
|
}
|
|
|
|
// perform update if flagged
|
|
if (flag_update != -1) {
|
|
// update with cholupd = J(i,:)*sqrt(D[i]))
|
|
if (ctx->is_sparse) {
|
|
// get nnz and adr of row i
|
|
const int nnz = ctx->J_rownnz[i], adr = ctx->J_rowadr[i];
|
|
|
|
// scale cholupd
|
|
mju_scl(cholupd, ctx->J+adr, mju_sqrt(ctx->efc_D[i]), nnz);
|
|
|
|
// sparse update or downdate
|
|
rank = mju_cholUpdateSparse(ctx->L, cholupd, nv, flag_update,
|
|
ctx->L_rownnz, ctx->L_rowadr, ctx->L_colind, nnz,
|
|
ctx->J_colind+adr, d);
|
|
} else {
|
|
mju_scl(cholupd, ctx->J+i*nv, mju_sqrt(ctx->efc_D[i]), nv);
|
|
rank = mju_cholUpdate(ctx->L, cholupd, nv, flag_update);
|
|
}
|
|
ctx->nupdate++;
|
|
|
|
// recompute H directly if accuracy lost
|
|
if (rank < nv) {
|
|
FactorizeHessian(d, ctx, /*flg_recompute=*/1);
|
|
|
|
// nothing else to do
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add cones if present
|
|
if (ctx->ncone) {
|
|
HessianCone(d, ctx);
|
|
}
|
|
}
|
|
|
|
|
|
// driver
|
|
static void mj_solPrimal(const mjModel* m, mjData* d, int island, int maxiter, int flg_Newton) {
|
|
int iter = 0;
|
|
mjtNum alpha, beta;
|
|
mjPrimalContext ctx;
|
|
mj_markStack(d);
|
|
|
|
// make context
|
|
PrimalPointers(m, d, &ctx, island);
|
|
PrimalAllocate(m, d, &ctx, flg_Newton);
|
|
|
|
// local copies
|
|
int nv = ctx.nv;
|
|
int nefc = ctx.nefc;
|
|
int* oldstate = ctx.oldstate;
|
|
|
|
// compute Ma = M * qacc
|
|
mju_mulSymVecSparse(ctx.Ma, ctx.M, ctx.qacc, nv,
|
|
ctx.M_rownnz, ctx.M_rowadr, ctx.M_colind);
|
|
|
|
|
|
// compute Jaref = J * qacc - aref (dense or sparse)
|
|
if (!ctx.is_sparse) {
|
|
mju_mulMatVec(ctx.Jaref, ctx.J, ctx.qacc, nefc, nv);
|
|
} else {
|
|
mju_mulMatVecSparse(ctx.Jaref, ctx.J, ctx.qacc, nefc,
|
|
ctx.J_rownnz, ctx.J_rowadr, ctx.J_colind, ctx.J_rowsuper);
|
|
}
|
|
mju_subFrom(ctx.Jaref, ctx.efc_aref, nefc);
|
|
|
|
// first update
|
|
PrimalUpdateConstraint(&ctx, flg_Newton & (m->opt.cone == mjCONE_ELLIPTIC));
|
|
if (flg_Newton) {
|
|
// compute and factorize Hessian
|
|
MakeHessian(d, &ctx);
|
|
FactorizeHessian(d, &ctx, /*flg_recompute=*/0);
|
|
}
|
|
PrimalUpdateGradient(&ctx, flg_Newton);
|
|
|
|
// start both with preconditioned gradient
|
|
mju_scl(ctx.search, ctx.Mgrad, -1, nv);
|
|
|
|
// compute and save scaling factor
|
|
mjtNum scale;
|
|
if (island < 0) {
|
|
scale = 1 / (m->stat.meaninertia * mjMAX(1, m->nv));
|
|
} else {
|
|
mjtNum island_inertia = 0;
|
|
for (int i=0; i < nv; i++) {
|
|
int diag_i = ctx.M_rowadr[i] + ctx.M_rownnz[i] - 1;
|
|
island_inertia += ctx.M[diag_i];
|
|
}
|
|
scale = 1 / island_inertia;
|
|
}
|
|
ctx.scale = scale;
|
|
|
|
// main loop
|
|
while (iter < maxiter) {
|
|
// perform linesearch
|
|
mjtNum ls_improvement;
|
|
alpha = PrimalSearch(&ctx, m->opt.tolerance * m->opt.ls_tolerance, m->opt.ls_iterations,
|
|
&ls_improvement);
|
|
|
|
// no improvement: done
|
|
if (alpha == 0) {
|
|
break;
|
|
}
|
|
|
|
// move to new solution
|
|
mju_addToScl(ctx.qacc, ctx.search, alpha, nv);
|
|
mju_addToScl(ctx.Ma, ctx.Mv, alpha, nv);
|
|
mju_addToScl(ctx.Jaref, ctx.Jv, alpha, nefc);
|
|
|
|
// save old
|
|
if (!flg_Newton) {
|
|
mju_copy(ctx.gradold, ctx.grad, nv);
|
|
mju_copy(ctx.Mgradold, ctx.Mgrad, nv);
|
|
}
|
|
mju_copyInt(oldstate, ctx.efc_state, nefc);
|
|
|
|
// update
|
|
PrimalUpdateConstraint(&ctx, flg_Newton & (m->opt.cone == mjCONE_ELLIPTIC));
|
|
if (flg_Newton) {
|
|
HessianIncremental(d, &ctx, oldstate);
|
|
}
|
|
PrimalUpdateGradient(&ctx, flg_Newton);
|
|
|
|
// count state changes
|
|
int nchange = 0;
|
|
for (int i=0; i < nefc; i++) {
|
|
nchange += (ctx.efc_state[i] != oldstate[i]);
|
|
}
|
|
|
|
// scale improvement, gradient, save stats
|
|
mjtNum improvement = scale * ls_improvement;
|
|
mjtNum gradient = scale * mju_norm(ctx.grad, nv);
|
|
saveStats(m, d, island, iter, improvement, gradient, ctx.LSslope,
|
|
ctx.nactive, nchange, ctx.LSiter, ctx.nupdate);
|
|
|
|
// increment iteration count
|
|
iter++;
|
|
|
|
// termination
|
|
if ((improvement > 0 && improvement < m->opt.tolerance) ||
|
|
gradient < m->opt.tolerance) {
|
|
break;
|
|
}
|
|
|
|
// update direction
|
|
if (flg_Newton) {
|
|
mju_scl(ctx.search, ctx.Mgrad, -1, nv);
|
|
} else {
|
|
#ifndef mjCG_PRP
|
|
// Hager-Zhang conjugate direction update
|
|
mjtNum d_dot_y, y_dot_My, y_dot_Mgrad, d_dot_grad;
|
|
mjtNum beta_hz;
|
|
mjtNum d_norm, grad_norm, eta_k;
|
|
const mjtNum eta = 0.01;
|
|
|
|
// graddif = grad - gradold, Mgraddif = Mgrad - Mgradold
|
|
mju_sub(ctx.graddif, ctx.grad, ctx.gradold, nv);
|
|
mju_sub(ctx.Mgraddif, ctx.Mgrad, ctx.Mgradold, nv);
|
|
|
|
// compute d'*y; restart to steepest descent if conjugacy is lost
|
|
d_dot_y = mju_dot(ctx.search, ctx.graddif, nv);
|
|
if (d_dot_y < mjMINVAL) {
|
|
beta = 0;
|
|
} else {
|
|
// compute remaining inner products for the HZ formula
|
|
y_dot_My = mju_dot(ctx.graddif, ctx.Mgraddif, nv);
|
|
y_dot_Mgrad = mju_dot(ctx.graddif, ctx.Mgrad, nv);
|
|
d_dot_grad = mju_dot(ctx.search, ctx.grad, nv);
|
|
|
|
// primary Hager-Zhang beta coefficient
|
|
beta_hz = (y_dot_Mgrad - 2*(y_dot_My/d_dot_y)*d_dot_grad) / d_dot_y;
|
|
|
|
// dynamic truncation threshold to ensure d is not orthogonal to grad
|
|
d_norm = mju_norm(ctx.search, nv);
|
|
grad_norm = mju_norm(ctx.grad, nv);
|
|
eta_k = -1.0 / mju_max(mjMINVAL, d_norm * mju_min(eta, grad_norm));
|
|
|
|
// apply lower bound
|
|
beta = mju_max(eta_k, beta_hz);
|
|
}
|
|
#else
|
|
// Polak-Ribiere-Plus conjugate direction update
|
|
mju_sub(ctx.Mgraddif, ctx.Mgrad, ctx.Mgradold, nv);
|
|
beta = mju_dot(ctx.grad, ctx.Mgraddif, nv) /
|
|
mju_max(mjMINVAL, mju_dot(ctx.gradold, ctx.Mgradold, nv));
|
|
|
|
// reset if negative
|
|
if (beta < 0) {
|
|
beta = 0;
|
|
}
|
|
#endif
|
|
|
|
// update
|
|
for (int i=0; i < nv; i++) {
|
|
ctx.search[i] = -ctx.Mgrad[i] + beta*ctx.search[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// finalize statistics
|
|
if (island < mjNISLAND) {
|
|
// if island is -1 (monolithic), clamp to 0
|
|
int island_stat = island < 0 ? 0 : island;
|
|
|
|
// update solver iterations
|
|
d->solver_niter[island_stat] += iter;
|
|
|
|
// set solver_nnz
|
|
if (flg_Newton) {
|
|
if (mj_isSparse(m)) {
|
|
// two L factors if Lcone is present
|
|
int num_factors = 1 + (ctx.Lcone != NULL);
|
|
d->solver_nnz[island_stat] = num_factors * ctx.nL + ctx.nH;
|
|
} else {
|
|
d->solver_nnz[island_stat] = nv*nv;
|
|
}
|
|
} else {
|
|
d->solver_nnz[island_stat] = ctx.nJ;
|
|
}
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
}
|
|
|
|
|
|
// CG entry point
|
|
void mj_solCG(const mjModel* m, mjData* d, int maxiter) {
|
|
mj_solPrimal(m, d, /*island=*/-1, maxiter, /*flg_Newton=*/0);
|
|
}
|
|
|
|
|
|
// CG entry point (one island)
|
|
void mj_solCG_island(const mjModel* m, mjData* d, int island, int maxiter) {
|
|
mj_solPrimal(m, d, island, maxiter, /*flg_Newton=*/0);
|
|
}
|
|
|
|
|
|
// Newton entry point
|
|
void mj_solNewton(const mjModel* m, mjData* d, int maxiter) {
|
|
mj_solPrimal(m, d, /*island=*/-1, maxiter, /*flg_Newton=*/1);
|
|
}
|
|
|
|
|
|
// Newton entry point (one island)
|
|
void mj_solNewton_island(const mjModel* m, mjData* d, int island, int maxiter) {
|
|
mj_solPrimal(m, d, island, maxiter, /*flg_Newton=*/1);
|
|
}
|