From 8fa0fea02d0646f831bae14debadc1921dc7a378 Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Mon, 3 Jun 2024 03:41:58 -0700 Subject: [PATCH] Allow partial specification of `polycoef` attribute of equality constraints. - Usually the user only wants to tweak `polycoef[1]`, after this change `"0 2"` is equivalent to `"0 2 0 0 0"`. - Also improve documentation of this attribute. - Tighten margins of documentation display equations. PiperOrigin-RevId: 639720742 Change-Id: Ibafa0e093cebe1c1c75bf2e47ff3bd2ad7619414 --- doc/XMLreference.rst | 14 ++++++---- doc/css/theme_overrides.css | 6 +++++ src/xml/xml_native_reader.cc | 4 +-- test/xml/xml_native_reader_test.cc | 42 +++++++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 86bff885..5f664c0f 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -4275,11 +4275,15 @@ joint types (slide and hinge) can be used. .. _equality-joint-polycoef: :at:`polycoef`: :at-val:`real(5), "0 1 0 0 0"` - Coefficients a0 ... a4 of the quartic polynomial. If the two joint values are y and x, and their reference positions - (corresponding to the joint values in the initial model configuration) are y0 and x0, the constraint is: - y-y0 = a0 + a1*(x-x0) + a2*(x-x0)^2 + a3*(x-x0)^3 + a4*(x-x0)^4. - Omitting the second joint is equivalent to setting x = x0, in which case the constraint is y = y0 + a0. + Coefficients :math:`a_0 \ldots a_4` of the quartic polynomial. If the joint values of :at:`joint1` and :at:`joint2` + are respectively :math:`y` and :math:`x`, and their reference positions (corresponding to the joint values in the + initial model configuration) are :math:`y_0` and :math:`x_0`, the constraint is: + .. math:: + y-y_0 = a_0 + a_1(x-x_0) + a_2(x-x_0)^2 + a_3(x-x_0)^3 + a_4(x-x_0)^4 + + Omitting :at:`joint2` is equivalent to setting :math:`x = x_0`, in which case the constraint is + :math:`y = y_0 + a_0`. .. _equality-tendon: @@ -4315,7 +4319,7 @@ This element constrains the length of one tendon to be a quartic polynomial of a .. _equality-tendon-polycoef: :at:`polycoef`: :at-val:`real(5), "0 1 0 0 0"` - Same as in the equality/ :ref:`joint ` element above, but applied to tendon lengths instead of joint + Same as in the :ref:`equality/joint ` element above, but applied to tendon lengths instead of joint positions. diff --git a/doc/css/theme_overrides.css b/doc/css/theme_overrides.css index 5d0bc476..6b607bda 100644 --- a/doc/css/theme_overrides.css +++ b/doc/css/theme_overrides.css @@ -260,6 +260,12 @@ dt .at { margin-left: 0.5em; } +/* Reduce top and bottom margins around displayed KaTeX equations */ +.katex-display { + margin-top: 0.3em; + margin-bottom: 0.3em; +} + details summary { font-weight: 600; } diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 2ea4ec75..92731593 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -1884,13 +1884,13 @@ void mjXReader::OneEquality(XMLElement* elem, mjsEquality* pequality) { case mjEQ_JOINT: ReadAttrTxt(elem, "joint1", name1, true); ReadAttrTxt(elem, "joint2", name2); - ReadAttr(elem, "polycoef", 5, pequality->data, text); + ReadAttr(elem, "polycoef", 5, pequality->data, text, false, false); break; case mjEQ_TENDON: ReadAttrTxt(elem, "tendon1", name1, true); ReadAttrTxt(elem, "tendon2", name2); - ReadAttr(elem, "polycoef", 5, pequality->data, text); + ReadAttr(elem, "polycoef", 5, pequality->data, text, false, false); break; case mjEQ_FLEX: diff --git a/test/xml/xml_native_reader_test.cc b/test/xml/xml_native_reader_test.cc index 12d38ce5..41a1ca73 100644 --- a/test/xml/xml_native_reader_test.cc +++ b/test/xml/xml_native_reader_test.cc @@ -34,8 +34,13 @@ namespace mujoco { namespace { +std::vector AsVector(const mjtNum* array, int n) { + return std::vector(array, array + n); +} + using ::std::string; using ::testing::AllOf; +using ::testing::ElementsAre; using ::testing::Eq; using ::testing::FloatEq; using ::testing::HasSubstr; @@ -834,8 +839,43 @@ TEST_F(XMLReaderTest, IncludeAbsoluteTest) { mj_deleteModel(model); } -// ------------------------ test frame parsing --------------------------------- +TEST_F(XMLReaderTest, ParsePolycoef) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + + + + + + + )"; + std::array error; + mjModel* m = LoadModelFromString(xml, error.data(), error.size()); + EXPECT_THAT(m, NotNull()) << error.data(); + EXPECT_THAT(AsVector(m->eq_data + 0*mjNEQDATA, 5), + ElementsAre(0, 1, 0, 0, 0)); + EXPECT_THAT(AsVector(m->eq_data + 1*mjNEQDATA, 5), + ElementsAre(2, 1, 0, 0, 0)); + EXPECT_THAT(AsVector(m->eq_data + 2*mjNEQDATA, 5), + ElementsAre(3, 4, 0, 0, 0)); + EXPECT_THAT(AsVector(m->eq_data + 3*mjNEQDATA, 5), + ElementsAre(5, 6, 7, 8, 9)); + mj_deleteModel(m); +} + +// ------------------------ test frame parsing --------------------------------- TEST_F(XMLReaderTest, ParseFrame) { static constexpr char xml[] = R"(