From 040d3efce2549700a812d8bc0178c329222180ef Mon Sep 17 00:00:00 2001 From: Google DeepMind Date: Wed, 6 Sep 2023 12:02:11 -0700 Subject: [PATCH] Add options for controlling early termination criteria of CG/Newton linesearch. Useful for performance tuning models. PiperOrigin-RevId: 563175078 Change-Id: Id64a510d6080038ea8d5876dabbf0b2b311435bb --- doc/XMLreference.rst | 12 ++++++++++++ doc/XMLschema.rst | 12 +++++++----- doc/changelog.rst | 6 ++++-- doc/includes/references.h | 2 ++ include/mujoco/mjmodel.h | 2 ++ include/mujoco/mjxmacro.h | 2 ++ introspect/structs.py | 10 ++++++++++ simulate/simulate.cc | 4 +++- src/engine/engine_io.c | 5 +++++ src/engine/engine_solver.c | 11 ++++------- src/xml/xml_native_reader.cc | 10 ++++++---- src/xml/xml_native_writer.cc | 2 ++ unity/Runtime/Bindings/MjBindings.cs | 2 ++ 13 files changed, 61 insertions(+), 19 deletions(-) diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 6ce1ce42..ad9f85ba 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -1972,6 +1972,18 @@ adjust it properly through the XML. improvement between two iterations. For CG and Newton, it is applied to the smaller of the cost improvement and the gradient norm. Set the tolerance to 0 to disable early termination. +.. _option-ls_iterations: + +:at:`ls_iterations`: :at-val:`int, "50"` + Maximum number of linesearch iterations performed by CG/Newton constraint solvers. Ensures that at most + :ref:`iterations` times :ref:`ls_iterations` linesearch iterations are + performed during each constraint solve. + +.. _option-ls_tolerance: + +:at:`ls_tolerance`: :at-val:`real, "0.01"` + Tolerance threshold used for early termination of the linesearch algorithm. + .. _option-noslip_iterations: :at:`noslip_iterations`: :at-val:`int, "0"` diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst index 2210168f..2951b722 100644 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -218,15 +218,17 @@ | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | | :ref:`timestep` | :ref:`apirate` | :ref:`impratio` | :ref:`tolerance` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`noslip_tolerance` | :ref:`mpr_tolerance` | :ref:`gravity` | :ref:`wind` | | +| | | | :ref:`ls_tolerance` | :ref:`noslip_tolerance` | :ref:`mpr_tolerance` | :ref:`gravity` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`magnetic` | :ref:`density` | :ref:`viscosity` | :ref:`o_margin` | | +| | | | :ref:`wind` | :ref:`magnetic` | :ref:`density` | :ref:`viscosity` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`o_solref` | :ref:`o_solimp` | :ref:`integrator` | :ref:`collision` | | +| | | | :ref:`o_margin` | :ref:`o_solref` | :ref:`o_solimp` | :ref:`integrator` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`cone` | :ref:`jacobian` | :ref:`solver` | :ref:`iterations` | | +| | | | :ref:`collision` | :ref:`cone` | :ref:`jacobian` | :ref:`solver` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`noslip_iterations` | :ref:`mpr_iterations` | :ref:`sdf_iterations` | :ref:`sdf_initpoints` | | +| | | | :ref:`iterations` | :ref:`ls_iterations` | :ref:`noslip_iterations` | :ref:`mpr_iterations` | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`sdf_iterations` | :ref:`sdf_initpoints` | | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_| option |br| |_| |L| | | .. table:: | diff --git a/doc/changelog.rst b/doc/changelog.rst index d166090e..e6d03de4 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -62,17 +62,19 @@ General Euler integrator. See the :ref:`Numerical Integration` section for more details. 11. Added the flag :ref:`invdiscrete`, which enables discrete-time inverse dynamics for all :ref:`integrators` other than ``RK4``. See the flag documentation for more details. +12. Added :ref:`ls_iterations` and :ref:`ls_iterations` options for adjusting + linesearch stopping criteria in CG and Newton solvers. This can be useful for performance tuning. Python bindings ^^^^^^^^^^^^^^^ -12. Fixed `#870 `__ where calling ``update_scene`` with an invalid +13. Fixed `#870 `__ where calling ``update_scene`` with an invalid camera name used the default camera. Bug fixes ^^^^^^^^^ -13. Fixed a bug that was causing the geom margins to be ignored during the midphase. +14. Fixed a bug that was causing the geom margins to be ignored during the midphase. Version 2.3.7 (July 20, 2023) diff --git a/doc/includes/references.h b/doc/includes/references.h index bf956b54..eda352fe 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -674,6 +674,7 @@ struct mjOption_ { // physics options // solver parameters mjtNum impratio; // ratio of friction-to-normal contact impedance mjtNum tolerance; // main solver tolerance + mjtNum ls_tolerance; // CG/Newton linesearch tolerance mjtNum noslip_tolerance; // noslip solver tolerance mjtNum mpr_tolerance; // MPR solver tolerance @@ -696,6 +697,7 @@ struct mjOption_ { // physics options int jacobian; // type of Jacobian (mjtJacobian) int solver; // solver algorithm (mjtSolver) int iterations; // maximum number of main solver iterations + int ls_iterations; // maximum number of CG/Newton linesearch iterations int noslip_iterations; // maximum number of noslip solver iterations int mpr_iterations; // maximum number of MPR solver iterations int disableflags; // bit flags for disabling standard features diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index 38f28f44..13b7fc5a 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -397,6 +397,7 @@ struct mjOption_ { // physics options // solver parameters mjtNum impratio; // ratio of friction-to-normal contact impedance mjtNum tolerance; // main solver tolerance + mjtNum ls_tolerance; // CG/Newton linesearch tolerance mjtNum noslip_tolerance; // noslip solver tolerance mjtNum mpr_tolerance; // MPR solver tolerance @@ -419,6 +420,7 @@ struct mjOption_ { // physics options int jacobian; // type of Jacobian (mjtJacobian) int solver; // solver algorithm (mjtSolver) int iterations; // maximum number of main solver iterations + int ls_iterations; // maximum number of CG/Newton linesearch iterations int noslip_iterations; // maximum number of noslip solver iterations int mpr_iterations; // maximum number of MPR solver iterations int disableflags; // bit flags for disabling standard features diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index 2ef1c7ee..842d7914 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -24,6 +24,7 @@ X( mjtNum, apirate ) \ X( mjtNum, impratio ) \ X( mjtNum, tolerance ) \ + X( mjtNum, ls_tolerance ) \ X( mjtNum, noslip_tolerance ) \ X( mjtNum, mpr_tolerance ) \ X( mjtNum, density ) \ @@ -38,6 +39,7 @@ X( int, jacobian ) \ X( int, solver ) \ X( int, iterations ) \ + X( int, ls_iterations ) \ X( int, noslip_iterations ) \ X( int, mpr_iterations ) \ X( int, disableflags ) \ diff --git a/introspect/structs.py b/introspect/structs.py index d399c6bc..2e5f08a0 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -148,6 +148,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='mjtNum'), doc='main solver tolerance', ), + StructFieldDecl( + name='ls_tolerance', + type=ValueType(name='mjtNum'), + doc='CG/Newton linesearch tolerance', + ), StructFieldDecl( name='noslip_tolerance', type=ValueType(name='mjtNum'), @@ -243,6 +248,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='int'), doc='maximum number of main solver iterations', ), + StructFieldDecl( + name='ls_iterations', + type=ValueType(name='int'), + doc='maximum number of CG/Newton linesearch iterations', + ), StructFieldDecl( name='noslip_iterations', type=ValueType(name='int'), diff --git a/simulate/simulate.cc b/simulate/simulate.cc index 404004dd..95fd574f 100644 --- a/simulate/simulate.cc +++ b/simulate/simulate.cc @@ -602,9 +602,11 @@ void MakePhysicsSection(mj::Simulate* sim, int oldstate) { {mjITEM_EDITNUM, "Timestep", 2, &(opt->timestep), "1 0 1"}, {mjITEM_EDITINT, "Iterations", 2, &(opt->iterations), "1 0 1000"}, {mjITEM_EDITNUM, "Tolerance", 2, &(opt->tolerance), "1 0 1"}, + {mjITEM_EDITINT, "LS Iter", 2, &(opt->ls_iterations), "1 0 100"}, + {mjITEM_EDITNUM, "LS Tol", 2, &(opt->ls_tolerance), "1 0 0.1"}, {mjITEM_EDITINT, "Noslip Iter", 2, &(opt->noslip_iterations), "1 0 1000"}, {mjITEM_EDITNUM, "Noslip Tol", 2, &(opt->noslip_tolerance), "1 0 1"}, - {mjITEM_EDITINT, "MRR Iter", 2, &(opt->mpr_iterations), "1 0 1000"}, + {mjITEM_EDITINT, "MPR Iter", 2, &(opt->mpr_iterations), "1 0 1000"}, {mjITEM_EDITNUM, "MPR Tol", 2, &(opt->mpr_tolerance), "1 0 1"}, {mjITEM_EDITNUM, "API Rate", 2, &(opt->apirate), "1 0 1000"}, {mjITEM_EDITINT, "SDF Iter", 2, &(opt->sdf_iterations), "1 1 20"}, diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c index 21324fde..4d1b3d22 100644 --- a/src/engine/engine_io.c +++ b/src/engine/engine_io.c @@ -113,6 +113,9 @@ void mj_defaultSolRefImp(mjtNum* solref, mjtNum* solimp) { // set model options to default values void mj_defaultOption(mjOption* opt) { + // fill opt with zeros in case struct is padded + memset(opt, 0, sizeof(mjOption)); + // timing parameters opt->timestep = 0.002; opt->apirate = 100; @@ -120,6 +123,7 @@ void mj_defaultOption(mjOption* opt) { // solver parameters opt->impratio = 1; opt->tolerance = 1e-8; + opt->ls_tolerance = 0.01; opt->noslip_tolerance = 1e-6; opt->mpr_tolerance = 1e-6; @@ -147,6 +151,7 @@ void mj_defaultOption(mjOption* opt) { opt->jacobian = mjJAC_AUTO; opt->solver = mjSOL_NEWTON; opt->iterations = 100; + opt->ls_iterations = 50; opt->noslip_iterations = 0; opt->mpr_iterations = 50; opt->disableflags = 0; diff --git a/src/engine/engine_solver.c b/src/engine/engine_solver.c index c0f1e313..7eeb41f2 100644 --- a/src/engine/engine_solver.c +++ b/src/engine/engine_solver.c @@ -1093,9 +1093,6 @@ static int updateBracket(const mjModel* m, mjData* d, mjCGContext* ctx, static mjtNum CGsearch(const mjModel* m, mjData* d, mjCGContext* ctx) { mjCGPnt p0, p1, p2, pmid, p1next, p2next; - const int LSmaxiter = 50; - const mjtNum LStolscl = 0.01; - // clear results ctx->LSiter = 0; ctx->LSresult = 0; @@ -1109,7 +1106,7 @@ static mjtNum CGsearch(const mjModel* m, mjData* d, mjCGContext* ctx) { } // compute scaled gradtol and slope scaling - mjtNum gtol = m->opt.tolerance * LStolscl * snorm * m->stat.meaninertia * mjMAX(1, m->nv); + mjtNum gtol = m->opt.tolerance * m->opt.ls_tolerance * snorm * m->stat.meaninertia * mjMAX(1, m->nv); mjtNum slopescl = 1 / (snorm * m->stat.meaninertia * mjMAX(1, m->nv)); // compute Mv, Jv @@ -1180,7 +1177,7 @@ static mjtNum CGsearch(const mjModel* m, mjData* d, mjCGContext* ctx) { // one-sided search int p2update = 0; - while (p1.deriv[0]*dir <= -gtol && ctx->LSiter < LSmaxiter) { + while (p1.deriv[0]*dir <= -gtol && ctx->LSiter < m->opt.ls_iterations) { // save current p2 = p1; p2update = 1; @@ -1197,7 +1194,7 @@ static mjtNum CGsearch(const mjModel* m, mjData* d, mjCGContext* ctx) { } // check for failure to bracket - if (ctx->LSiter >= LSmaxiter) { + if (ctx->LSiter >= m->opt.ls_iterations) { ctx->LSresult = 3; // could not bracket ctx->LSslope = mju_abs(p1.deriv[0])*slopescl; return p1.alpha; @@ -1216,7 +1213,7 @@ static mjtNum CGsearch(const mjModel* m, mjData* d, mjCGContext* ctx) { CGeval(m, d, ctx, &p1next); // bracketed search - while (ctx->LSiter < LSmaxiter) { + while (ctx->LSiter < m->opt.ls_iterations) { // evaluate at midpoint pmid.alpha = 0.5*(p1.alpha + p2.alpha); CGeval(m, d, ctx, &pmid); diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 3026dfb5..dc5129d6 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -95,12 +95,12 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = { "inttotal", "interval", "tolrange"}, {">"}, - {"option", "*", "24", - "timestep", "apirate", "impratio", "tolerance", "noslip_tolerance", "mpr_tolerance", - "gravity", "wind", "magnetic", "density", "viscosity", + {"option", "*", "26", + "timestep", "apirate", "impratio", "tolerance", "ls_tolerance", "noslip_tolerance", + "mpr_tolerance", "gravity", "wind", "magnetic", "density", "viscosity", "o_margin", "o_solref", "o_solimp", "integrator", "collision", "cone", "jacobian", - "solver", "iterations", "noslip_iterations", "mpr_iterations", + "solver", "iterations", "ls_iterations", "noslip_iterations", "mpr_iterations", "sdf_iterations", "sdf_initpoints"}, {"<"}, {"flag", "?", "22", "constraint", "equality", "frictionloss", "limit", "contact", @@ -958,6 +958,7 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) { ReadAttr(section, "apirate", 1, &opt->apirate, text); ReadAttr(section, "impratio", 1, &opt->impratio, text); ReadAttr(section, "tolerance", 1, &opt->tolerance, text); + ReadAttr(section, "ls_tolerance", 1, &opt->ls_tolerance, text); ReadAttr(section, "noslip_tolerance", 1, &opt->noslip_tolerance, text); ReadAttr(section, "mpr_tolerance", 1, &opt->mpr_tolerance, text); ReadAttr(section, "gravity", 3, opt->gravity, text); @@ -976,6 +977,7 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) { MapValue(section, "jacobian", &opt->jacobian, jac_map, jac_sz); MapValue(section, "solver", &opt->solver, solver_map, solver_sz); ReadAttrInt(section, "iterations", &opt->iterations); + ReadAttrInt(section, "ls_iterations", &opt->ls_iterations); ReadAttrInt(section, "noslip_iterations", &opt->noslip_iterations); ReadAttrInt(section, "mpr_iterations", &opt->mpr_iterations); ReadAttrInt(section, "sdf_iterations", &opt->sdf_iterations); diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index 31c3a1e4..bcd0ffff 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -793,6 +793,7 @@ void mjXWriter::Option(XMLElement* root) { WriteAttr(section, "apirate", 1, &model->option.apirate, &opt.apirate); WriteAttr(section, "impratio", 1, &model->option.impratio, &opt.impratio); WriteAttr(section, "tolerance", 1, &model->option.tolerance, &opt.tolerance); + WriteAttr(section, "ls_tolerance", 1, &model->option.ls_tolerance, &opt.ls_tolerance); WriteAttr(section, "noslip_tolerance", 1, &model->option.noslip_tolerance, &opt.noslip_tolerance); WriteAttr(section, "mpr_tolerance", 1, &model->option.mpr_tolerance, &opt.mpr_tolerance); WriteAttr(section, "gravity", 3, model->option.gravity, opt.gravity); @@ -816,6 +817,7 @@ void mjXWriter::Option(XMLElement* root) { WriteAttrKey(section, "solver", solver_map, solver_sz, model->option.solver, opt.solver); WriteAttrInt(section, "iterations", model->option.iterations, opt.iterations); + WriteAttrInt(section, "ls_iterations", model->option.ls_iterations, opt.ls_iterations); WriteAttrInt(section, "noslip_iterations", model->option.noslip_iterations, opt.noslip_iterations); WriteAttrInt(section, "mpr_iterations", model->option.mpr_iterations, opt.mpr_iterations); WriteAttrInt(section, "sdf_iterations", model->option.sdf_iterations, opt.sdf_iterations); diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 475a894d..2500e8a6 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -1769,6 +1769,7 @@ public unsafe struct mjOption_ { public double apirate; public double impratio; public double tolerance; + public double ls_tolerance; public double noslip_tolerance; public double mpr_tolerance; public fixed double gravity[3]; @@ -1785,6 +1786,7 @@ public unsafe struct mjOption_ { public int jacobian; public int solver; public int iterations; + public int ls_iterations; public int noslip_iterations; public int mpr_iterations; public int disableflags;