Skip to content

Commit cf51fce

Browse files
committed
Expose RBDL zero moment point
1 parent b324837 commit cf51fce

6 files changed

Lines changed: 185 additions & 0 deletions

File tree

binding/python3/wrapper/biorbd_model.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,44 @@ def center_of_mass(self, q: BiorbdArray | None = None) -> BiorbdArray:
146146
update_kinematics = False
147147
return to_biorbd_array_output(updated_model.CoM(dummy_q, update_kinematics))
148148

149+
def zero_moment_point(
150+
self,
151+
q: BiorbdArray,
152+
qdot: BiorbdArray,
153+
qddot: BiorbdArray,
154+
normal: BiorbdArray = (0, 0, 1),
155+
point: BiorbdArray = (0, 0, 0),
156+
) -> BiorbdArray:
157+
"""
158+
Get the zero moment point projected on a contact surface.
159+
160+
Parameters
161+
----------
162+
q: BiorbdArray
163+
Generalized coordinates
164+
qdot: BiorbdArray
165+
Generalized velocities
166+
qddot: BiorbdArray
167+
Generalized accelerations
168+
normal: BiorbdArray
169+
Normal of the contact surface
170+
point: BiorbdArray
171+
A point on the contact surface
172+
173+
Returns
174+
-------
175+
The zero moment point in the base frame
176+
"""
177+
return to_biorbd_array_output(
178+
self.internal.CalcZeroMomentPoint(
179+
to_biorbd_array_input(q),
180+
to_biorbd_array_input(qdot),
181+
to_biorbd_array_input(qddot),
182+
to_biorbd_array_input(normal),
183+
to_biorbd_array_input(point),
184+
)
185+
)
186+
149187
@property
150188
def segments(self) -> SegmentsList:
151189
"""

include/RigidBody/Joints.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,38 @@ class BIORBD_API Joints : public RigidBodyDynamics::Model
716716
utils::Vector3d* changeOfAngularMomentum = NULL,
717717
bool updateKin = true);
718718

719+
///
720+
/// \brief Compute the zero moment point on a contact surface.
721+
/// \param Q The current joint positions
722+
/// \param Qdot The current joint velocities
723+
/// \param Qddot The current joint accelerations
724+
/// \param updateKin If the kinematics of the model should be computed
725+
/// \return The Zero Moment Point projected on the horizontal surface
726+
///
727+
utils::Vector3d CalcZeroMomentPoint(
728+
const rigidbody::GeneralizedCoordinates& Q,
729+
const rigidbody::GeneralizedVelocity& Qdot,
730+
const rigidbody::GeneralizedAcceleration& Qddot,
731+
bool updateKin = true);
732+
733+
///
734+
/// \brief Compute the zero moment point on a contact surface.
735+
/// \param Q The current joint positions
736+
/// \param Qdot The current joint velocities
737+
/// \param Qddot The current joint accelerations
738+
/// \param normal The normal of the contact surface
739+
/// \param point A point on the contact surface
740+
/// \param updateKin If the kinematics of the model should be computed
741+
/// \return The Zero Moment Point projected on the contact surface
742+
///
743+
utils::Vector3d CalcZeroMomentPoint(
744+
const rigidbody::GeneralizedCoordinates& Q,
745+
const rigidbody::GeneralizedVelocity& Qdot,
746+
const rigidbody::GeneralizedAcceleration& Qddot,
747+
const utils::Vector3d& normal,
748+
const utils::Vector3d& point,
749+
bool updateKin = true);
750+
719751
///
720752
/// \brief Return the position of the center of mass of each segment
721753
/// \param Q The generalized coordinates

src/RigidBody/Joints.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,44 @@ void rigidbody::Joints::CalcCenterOfMass(
10351035
false);
10361036
}
10371037

1038+
utils::Vector3d rigidbody::Joints::CalcZeroMomentPoint(
1039+
const rigidbody::GeneralizedCoordinates &Q,
1040+
const rigidbody::GeneralizedVelocity &Qdot,
1041+
const rigidbody::GeneralizedAcceleration &Qddot,
1042+
bool updateKin) {
1043+
return CalcZeroMomentPoint(
1044+
Q,
1045+
Qdot,
1046+
Qddot,
1047+
utils::Vector3d(0., 0., 1.),
1048+
utils::Vector3d(0., 0., 0.),
1049+
updateKin);
1050+
}
1051+
1052+
utils::Vector3d rigidbody::Joints::CalcZeroMomentPoint(
1053+
const rigidbody::GeneralizedCoordinates &Q,
1054+
const rigidbody::GeneralizedVelocity &Qdot,
1055+
const rigidbody::GeneralizedAcceleration &Qddot,
1056+
const utils::Vector3d &normal,
1057+
const utils::Vector3d &point,
1058+
bool updateKin) {
1059+
#ifdef BIORBD_USE_CASADI_MATH
1060+
rigidbody::Joints
1061+
#else
1062+
rigidbody::Joints &
1063+
#endif
1064+
updatedModel = this->UpdateKinematicsCustom(
1065+
updateKin ? &Q : nullptr,
1066+
updateKin ? &Qdot : nullptr,
1067+
updateKin ? &Qddot : nullptr);
1068+
1069+
utils::Vector3d zmp;
1070+
RigidBodyDynamics::Utils::CalcZeroMomentPoint(
1071+
updatedModel, Q, Qdot, Qddot, &zmp, normal, point, false);
1072+
1073+
return zmp;
1074+
}
1075+
10381076
std::vector<rigidbody::NodeSegment> rigidbody::Joints::CoMbySegment(
10391077
const rigidbody::GeneralizedCoordinates &Q,
10401078
bool updateKin) {

test/binding/python3/test_binder_python_rigidbody.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,42 @@ def test_com(brbd):
243243
np.testing.assert_almost_equal(com_ddot.squeeze(), expected_com_ddot)
244244

245245

246+
@pytest.mark.parametrize("brbd", brbd_to_test)
247+
def test_zero_moment_point(brbd):
248+
m = brbd.Model("../../models/pyomecaman.bioMod")
249+
250+
q = np.array([0.1, 0.1, 0.1, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3])
251+
q_dot = np.array([1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
252+
q_ddot = np.array([10, 10, 10, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30])
253+
normal = np.array([0, 0, 1])
254+
point = np.array([0, 0, 0.1])
255+
256+
if brbd.backend == brbd.CASADI:
257+
from casadi import MX
258+
259+
q_sym = MX.sym("q", m.nbQ(), 1)
260+
q_dot_sym = MX.sym("q_dot", m.nbQdot(), 1)
261+
q_ddot_sym = MX.sym("q_ddot", m.nbQddot(), 1)
262+
263+
zero_moment_point_func = brbd.to_casadi_func(
264+
"Compute_Zero_Moment_Point",
265+
m.CalcZeroMomentPoint,
266+
q_sym,
267+
q_dot_sym,
268+
q_ddot_sym,
269+
normal,
270+
point,
271+
)
272+
zero_moment_point = np.array(zero_moment_point_func(q, q_dot, q_ddot))
273+
274+
elif brbd.backend == brbd.EIGEN3:
275+
zero_moment_point = m.CalcZeroMomentPoint(q, q_dot, q_ddot, normal, point).to_array()
276+
else:
277+
raise NotImplementedError("Backend not implemented in test")
278+
279+
np.testing.assert_almost_equal(zero_moment_point.squeeze()[2], point[2])
280+
281+
246282
@pytest.mark.parametrize("brbd", brbd_to_test)
247283
def test_set_vector3d(brbd):
248284
m = brbd.Model("../../models/pyomecaman.bioMod")

test/binding/python3/tests_wrapper/test_wrapper_dynamics.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ def test_wrapper_dynamics(brbd):
5656
],
5757
decimal=6,
5858
)
59+
np.testing.assert_almost_equal(
60+
evaluate(
61+
brbd,
62+
model.zero_moment_point,
63+
q=q,
64+
qdot=qdot,
65+
qddot=qddot,
66+
normal=np.array([0, 0, 1]),
67+
point=np.array([0, 0, 0.1]),
68+
)[2],
69+
0.1,
70+
)
5971

6072

6173
if __name__ == "__main__":

test/test_rigidbody.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <rbdl/Dynamics.h>
88
#include <rbdl/rbdl_math.h>
9+
#include <rbdl/rbdl_utils.h>
910

1011
#include "BiorbdModel.h"
1112
#include "RigidBody/ExternalForceSet.h"
@@ -1467,6 +1468,34 @@ TEST(CoM, kinematics) {
14671468
}
14681469
}
14691470

1471+
TEST(CoM, zeroMomentPoint) {
1472+
Model model(modelPathForGeneralTesting);
1473+
Model expectedModel(modelPathForGeneralTesting);
1474+
rigidbody::GeneralizedCoordinates Q(model);
1475+
rigidbody::GeneralizedVelocity Qdot(model);
1476+
rigidbody::GeneralizedAcceleration Qddot(model);
1477+
for (size_t i = 0; i < model.nbQ(); ++i) {
1478+
Q(i, 0) = QtestPyomecaman[i];
1479+
Qdot(i, 0) = QtestPyomecaman[i] * 10;
1480+
Qddot(i, 0) = QtestPyomecaman[i] * 100;
1481+
}
1482+
1483+
utils::Vector3d normal(0, 0, 1);
1484+
utils::Vector3d point(0, 0, 0);
1485+
utils::Vector3d zeroMomentPoint(
1486+
model.CalcZeroMomentPoint(Q, Qdot, Qddot, normal, point));
1487+
utils::Vector3d expectedZeroMomentPoint;
1488+
RigidBodyDynamics::Utils::CalcZeroMomentPoint(
1489+
expectedModel, Q, Qdot, Qddot, &expectedZeroMomentPoint, normal, point);
1490+
1491+
for (size_t i = 0; i < 3; ++i) {
1492+
EXPECT_NEAR(
1493+
static_cast<double>(zeroMomentPoint(i, 0)),
1494+
static_cast<double>(expectedZeroMomentPoint(i, 0)),
1495+
requiredPrecision);
1496+
}
1497+
}
1498+
14701499
TEST(Segment, copy) {
14711500
Model model(modelPathForGeneralTesting);
14721501
rigidbody::SegmentCharacteristics characteristics(

0 commit comments

Comments
 (0)