GCC Code Coverage Report


Directory: ./
File: unittest/test_friction_cone.cpp
Date: 2025-06-03 08:14:12
Exec Total Coverage
Lines: 0 206 0.0%
Functions: 0 9 0.0%
Branches: 0 1600 0.0%

Line Branch Exec Source
1 ///////////////////////////////////////////////////////////////////////////////
2 // BSD 3-Clause License
3 //
4 // Copyright (C) 2019-2025, University of Edinburgh, University of Oxford,
5 // Heriot-Watt University
6 // Copyright note valid unless otherwise stated in individual files.
7 // All rights reserved.
8 ///////////////////////////////////////////////////////////////////////////////
9
10 #define BOOST_TEST_NO_MAIN
11 #define BOOST_TEST_ALTERNATIVE_INIT_API
12
13 #include <pinocchio/math/quaternion.hpp>
14
15 #include "crocoddyl/core/activations/quadratic-barrier.hpp"
16 #include "crocoddyl/multibody/friction-cone.hpp"
17 #include "unittest_common.hpp"
18
19 using namespace boost::unit_test;
20 using namespace crocoddyl::unittest;
21
22 ✗ void test_constructor() {
23 // Common parameters
24 ✗ double mu = random_real_in_range(0.01, 1.);
25 ✗ std::size_t nf = 2 * random_int_in_range(2, 16);
26 ✗ bool inner_appr = false;
27
28 // No rotation
29 ✗ Eigen::Matrix3d R = Eigen::Matrix3d::Identity();
30
31 // Create the friction cone with rotation and surface normal
32 ✗ crocoddyl::FrictionCone cone(R, mu, nf, inner_appr);
33 ✗ crocoddyl::FrictionCone casted_cone = cone.cast<double>();
34
35 ✗ BOOST_CHECK(cone.get_R().isApprox(R));
36 ✗ BOOST_CHECK(cone.get_mu() == mu);
37 ✗ BOOST_CHECK(cone.get_nf() == nf);
38 ✗ BOOST_CHECK(cone.get_inner_appr() == inner_appr);
39 ✗ BOOST_CHECK(static_cast<std::size_t>(cone.get_A().rows()) == nf + 1);
40 ✗ BOOST_CHECK(static_cast<std::size_t>(cone.get_lb().size()) == nf + 1);
41 ✗ BOOST_CHECK(static_cast<std::size_t>(cone.get_ub().size()) == nf + 1);
42
43 // Checking that casted computation is the same
44 ✗ BOOST_CHECK(casted_cone.get_R().isApprox(R));
45 ✗ BOOST_CHECK(casted_cone.get_mu() == mu);
46 ✗ BOOST_CHECK(casted_cone.get_nf() == nf);
47 ✗ BOOST_CHECK(casted_cone.get_inner_appr() == inner_appr);
48 ✗ BOOST_CHECK(static_cast<std::size_t>(casted_cone.get_A().rows()) == nf + 1);
49 ✗ BOOST_CHECK(static_cast<std::size_t>(casted_cone.get_lb().size()) == nf + 1);
50 ✗ BOOST_CHECK(static_cast<std::size_t>(casted_cone.get_ub().size()) == nf + 1);
51
52 // With rotation
53 ✗ Eigen::Quaterniond q;
54 ✗ pinocchio::quaternion::uniformRandom(q);
55 ✗ R = q.toRotationMatrix();
56
57 // Create the friction cone
58 ✗ cone = crocoddyl::FrictionCone(R, mu, nf, inner_appr);
59 ✗ casted_cone = cone.cast<double>();
60
61 ✗ BOOST_CHECK(cone.get_R().isApprox(R));
62 ✗ BOOST_CHECK(cone.get_mu() == mu);
63 ✗ BOOST_CHECK(cone.get_nf() == nf);
64 ✗ BOOST_CHECK(cone.get_inner_appr() == inner_appr);
65 ✗ BOOST_CHECK(static_cast<std::size_t>(cone.get_A().rows()) == nf + 1);
66 ✗ BOOST_CHECK(static_cast<std::size_t>(cone.get_lb().size()) == nf + 1);
67 ✗ BOOST_CHECK(static_cast<std::size_t>(cone.get_ub().size()) == nf + 1);
68
69 // Checking that casted computation is the same
70 ✗ BOOST_CHECK(casted_cone.get_R().isApprox(R));
71 ✗ BOOST_CHECK(casted_cone.get_mu() == mu);
72 ✗ BOOST_CHECK(casted_cone.get_nf() == nf);
73 ✗ BOOST_CHECK(casted_cone.get_inner_appr() == inner_appr);
74 ✗ BOOST_CHECK(static_cast<std::size_t>(casted_cone.get_A().rows()) == nf + 1);
75 ✗ BOOST_CHECK(static_cast<std::size_t>(casted_cone.get_lb().size()) == nf + 1);
76 ✗ BOOST_CHECK(static_cast<std::size_t>(casted_cone.get_ub().size()) == nf + 1);
77
78 // Create the friction cone from a reference
79 {
80 ✗ crocoddyl::FrictionCone cone_reference(cone);
81 ✗ casted_cone = cone_reference.cast<double>();
82
83 ✗ BOOST_CHECK(cone.get_nf() == cone_reference.get_nf());
84 ✗ BOOST_CHECK(cone.get_A().isApprox(cone_reference.get_A()));
85 ✗ for (std::size_t i = 0; i < static_cast<std::size_t>(cone.get_ub().size());
86 ++i) {
87 ✗ BOOST_CHECK(cone.get_ub()[i] == cone_reference.get_ub()[i]);
88 ✗ BOOST_CHECK(cone.get_lb()[i] == cone_reference.get_lb()[i]);
89 }
90 ✗ BOOST_CHECK(cone.get_R().isApprox(cone_reference.get_R()));
91 ✗ BOOST_CHECK(std::abs(cone.get_mu() - cone_reference.get_mu()) < 1e-9);
92 ✗ BOOST_CHECK(cone.get_inner_appr() == cone_reference.get_inner_appr());
93 ✗ BOOST_CHECK(std::abs(cone.get_min_nforce() -
94 cone_reference.get_min_nforce()) < 1e-9);
95 ✗ BOOST_CHECK(cone.get_max_nforce() == cone_reference.get_max_nforce());
96
97 // Checking that casted computation is the same
98 ✗ BOOST_CHECK(casted_cone.get_nf() == cone_reference.get_nf());
99 ✗ BOOST_CHECK(casted_cone.get_A().isApprox(cone_reference.get_A()));
100 ✗ for (std::size_t i = 0; i < static_cast<std::size_t>(cone.get_ub().size());
101 ++i) {
102 ✗ BOOST_CHECK(casted_cone.get_ub()[i] == cone_reference.get_ub()[i]);
103 ✗ BOOST_CHECK(casted_cone.get_lb()[i] == cone_reference.get_lb()[i]);
104 }
105 ✗ BOOST_CHECK(casted_cone.get_R().isApprox(cone_reference.get_R()));
106 ✗ BOOST_CHECK(std::abs(casted_cone.get_mu() - cone_reference.get_mu()) <
107 1e-9);
108 ✗ BOOST_CHECK(cone.get_inner_appr() == cone_reference.get_inner_appr());
109 ✗ BOOST_CHECK(std::abs(casted_cone.get_min_nforce() -
110 cone_reference.get_min_nforce()) < 1e-9);
111 ✗ BOOST_CHECK(casted_cone.get_max_nforce() ==
112 cone_reference.get_max_nforce());
113 ✗ }
114
115 // Create the friction cone through the copy operator
116 {
117 ✗ crocoddyl::FrictionCone cone_copy;
118 ✗ cone_copy = cone;
119 ✗ casted_cone = cone_copy.cast<double>();
120
121 ✗ BOOST_CHECK(cone.get_nf() == cone_copy.get_nf());
122 ✗ BOOST_CHECK(cone.get_A().isApprox(cone_copy.get_A()));
123 ✗ for (std::size_t i = 0; i < static_cast<std::size_t>(cone.get_ub().size());
124 ++i) {
125 ✗ BOOST_CHECK(cone.get_ub()[i] == cone_copy.get_ub()[i]);
126 ✗ BOOST_CHECK(cone.get_lb()[i] == cone_copy.get_lb()[i]);
127 }
128 ✗ BOOST_CHECK(cone.get_R().isApprox(cone_copy.get_R()));
129 ✗ BOOST_CHECK(std::abs(cone.get_mu() - cone_copy.get_mu()) < 1e-9);
130 ✗ BOOST_CHECK(cone.get_inner_appr() == cone_copy.get_inner_appr());
131 ✗ BOOST_CHECK(std::abs(cone.get_min_nforce() - cone_copy.get_min_nforce()) <
132 1e-9);
133 ✗ BOOST_CHECK(cone.get_max_nforce() == cone_copy.get_max_nforce());
134
135 // Checking that casted computation is the same
136 ✗ BOOST_CHECK(casted_cone.get_nf() == cone_copy.get_nf());
137 ✗ BOOST_CHECK(casted_cone.get_A().isApprox(cone_copy.get_A()));
138 ✗ for (std::size_t i = 0; i < static_cast<std::size_t>(cone.get_ub().size());
139 ++i) {
140 ✗ BOOST_CHECK(casted_cone.get_ub()[i] == cone_copy.get_ub()[i]);
141 ✗ BOOST_CHECK(casted_cone.get_lb()[i] == cone_copy.get_lb()[i]);
142 }
143 ✗ BOOST_CHECK(casted_cone.get_R().isApprox(cone_copy.get_R()));
144 ✗ BOOST_CHECK(std::abs(casted_cone.get_mu() - cone_copy.get_mu()) < 1e-9);
145 ✗ BOOST_CHECK(casted_cone.get_inner_appr() == cone_copy.get_inner_appr());
146 ✗ BOOST_CHECK(std::abs(casted_cone.get_min_nforce() -
147 cone_copy.get_min_nforce()) < 1e-9);
148 ✗ BOOST_CHECK(casted_cone.get_max_nforce() == cone_copy.get_max_nforce());
149 ✗ }
150 ✗ }
151
152 ✗ void test_inner_approximation_of_friction_cone() {
153 ✗ Eigen::Matrix3d R = Eigen::Matrix3d::Identity();
154 ✗ double mu = random_real_in_range(0.01, 1.);
155 ✗ std::size_t nf = 2 * random_int_in_range(2, 16);
156 ✗ bool inner_appr = true;
157 ✗ crocoddyl::FrictionCone cone(R, mu, nf, inner_appr);
158 ✗ crocoddyl::FrictionCone casted_cone = cone.cast<double>();
159 ✗ const Eigen::VectorXd A_mu = -cone.get_A().col(2);
160 ✗ for (std::size_t i = 0; i < nf; ++i) {
161 ✗ BOOST_CHECK_CLOSE(
162 A_mu(i),
163 mu * cos((2 * crocoddyl::pi<double>() / static_cast<double>(nf)) / 2.),
164 1e-9);
165 }
166
167 // Checking that casted computation is the same
168 ✗ const Eigen::VectorXd A_mu_casted = -cone.get_A().col(2);
169 ✗ for (std::size_t i = 0; i < nf; ++i) {
170 ✗ BOOST_CHECK_CLOSE(
171 A_mu_casted(i),
172 mu * cos((2 * crocoddyl::pi<double>() / static_cast<double>(nf)) / 2.),
173 1e-9);
174 }
175 ✗ }
176
177 ✗ void test_A_matrix_with_rotation_change() {
178 // Common parameters
179 ✗ double mu = random_real_in_range(0.01, 1.);
180 ✗ std::size_t nf = 2 * random_int_in_range(2, 16);
181 ✗ bool inner_appr = false;
182
183 // No rotation
184 ✗ Eigen::Matrix3d R = Eigen::Matrix3d::Identity();
185 ✗ crocoddyl::FrictionCone cone_1(R, mu, nf, inner_appr);
186 ✗ crocoddyl::FrictionCone casted_cone_1 = cone_1.cast<double>();
187
188 // With rotation
189 ✗ Eigen::Quaterniond q;
190 ✗ pinocchio::quaternion::uniformRandom(q);
191 ✗ R = q.toRotationMatrix();
192 ✗ crocoddyl::FrictionCone cone_2(R, mu, nf, inner_appr);
193 ✗ crocoddyl::FrictionCone casted_cone_2 = cone_2.cast<double>();
194
195 ✗ for (std::size_t i = 0; i < 5; ++i) {
196 ✗ BOOST_CHECK(
197 (cone_1.get_A().row(i) - cone_2.get_A().row(i) * R).isZero(1e-9));
198 }
199
200 // Checking that casted computation is the same
201 ✗ for (std::size_t i = 0; i < 5; ++i) {
202 ✗ BOOST_CHECK(
203 (casted_cone_1.get_A().row(i) - casted_cone_2.get_A().row(i) * R)
204 .isZero(1e-9));
205 }
206 ✗ }
207
208 ✗ void test_force_along_friction_cone_normal() {
209 // Create the friction cone
210 ✗ Eigen::Quaterniond q;
211 ✗ pinocchio::quaternion::uniformRandom(q);
212 ✗ Eigen::Matrix3d R = q.toRotationMatrix();
213 ✗ double mu = random_real_in_range(0.01, 1.);
214 ✗ std::size_t nf = 2 * random_int_in_range(2, 16);
215 ✗ bool inner_appr = false;
216 ✗ crocoddyl::FrictionCone cone(R, mu, nf, inner_appr);
217
218 // Create the activation for quadratic barrier
219 ✗ crocoddyl::ActivationBounds bounds(cone.get_lb(), cone.get_ub());
220 ✗ crocoddyl::ActivationModelQuadraticBarrier activation(bounds);
221 crocoddyl::ActivationModelQuadraticBarrier casted_activation =
222 ✗ activation.cast<double>();
223 const std::shared_ptr<crocoddyl::ActivationDataAbstract>& data =
224 ✗ activation.createData();
225 const std::shared_ptr<crocoddyl::ActivationDataAbstract>& casted_data =
226 ✗ casted_activation.createData();
227
228 // Compute the activation value
229 ✗ Eigen::Vector3d force = random_real_in_range(0., 100.) * R.col(2);
230 ✗ Eigen::VectorXd r = cone.get_A() * force;
231 ✗ activation.calc(data, r);
232
233 // The activation value has to be zero since the force is inside the friction
234 // cone
235 ✗ BOOST_CHECK(data->a_value == 0.);
236
237 // Checking that casted computation is the same
238 ✗ casted_activation.calc(casted_data, r);
239 ✗ BOOST_CHECK(casted_data->a_value == 0.);
240 ✗ }
241
242 ✗ void test_negative_force_along_friction_cone_normal() {
243 // Create the friction cone
244 ✗ Eigen::Quaterniond q;
245 ✗ pinocchio::quaternion::uniformRandom(q);
246 ✗ Eigen::Matrix3d R = q.toRotationMatrix();
247 ✗ double mu = random_real_in_range(0.01, 1.);
248 ✗ std::size_t nf = 2 * random_int_in_range(2, 16);
249 ✗ bool inner_appr = false;
250 ✗ crocoddyl::FrictionCone cone(R, mu, nf, inner_appr);
251 ✗ crocoddyl::FrictionCone casted_cone = cone.cast<double>();
252
253 // Create the activation for quadratic barrier
254 ✗ crocoddyl::ActivationBounds bounds(cone.get_lb(), cone.get_ub());
255 ✗ crocoddyl::ActivationModelQuadraticBarrier activation(bounds);
256 crocoddyl::ActivationModelQuadraticBarrier casted_activation =
257 ✗ activation.cast<double>();
258 const std::shared_ptr<crocoddyl::ActivationDataAbstract>& data =
259 ✗ activation.createData();
260 const std::shared_ptr<crocoddyl::ActivationDataAbstract>& casted_data =
261 ✗ casted_activation.createData();
262
263 // Compute the activation value
264 ✗ Eigen::Vector3d force = -random_real_in_range(0., 100.) * R.col(2);
265 ✗ Eigen::VectorXd r = cone.get_A() * force;
266
267 // The first nf elements of the residual has to be positive since the force is
268 // outside the friction cone. Additionally, the last value has to be equals to
269 // the force norm but with negative value since the forces is aligned and in
270 // opposite direction to the friction cone orientation
271 ✗ for (std::size_t i = 0; i < nf; ++i) {
272 ✗ BOOST_CHECK(r(i) > 0.);
273 }
274 ✗ BOOST_CHECK_CLOSE(r(nf), -force.norm(), 1e-9);
275
276 // The activation value has to be positive since the force is outside the
277 // friction cone
278 ✗ activation.calc(data, r);
279 ✗ BOOST_CHECK(data->a_value > 0.);
280
281 // Checking that casted computation is the same
282 ✗ r = casted_cone.get_A() * force;
283 ✗ for (std::size_t i = 0; i < nf; ++i) {
284 ✗ BOOST_CHECK(r(i) > 0.);
285 }
286 ✗ BOOST_CHECK_CLOSE(r(nf), -force.norm(), 1e-9);
287 ✗ casted_activation.calc(casted_data, r);
288 ✗ BOOST_CHECK(casted_data->a_value > 0.);
289 ✗ }
290
291 ✗ void test_force_parallel_to_friction_cone_normal() {
292 // Create the friction cone
293 ✗ Eigen::Matrix3d R = Eigen::Matrix3d::Identity();
294 ✗ double mu = random_real_in_range(0.01, 1.);
295 ✗ std::size_t nf = 2 * random_int_in_range(2, 16);
296 ✗ bool inner_appr = false;
297 ✗ crocoddyl::FrictionCone cone(R, mu, nf, inner_appr);
298 ✗ crocoddyl::FrictionCone casted_cone = cone.cast<double>();
299
300 // Create the activation for quadratic barrier
301 ✗ crocoddyl::ActivationBounds bounds(cone.get_lb(), cone.get_ub());
302 ✗ crocoddyl::ActivationModelQuadraticBarrier activation(bounds);
303 crocoddyl::ActivationModelQuadraticBarrier casted_activation =
304 ✗ activation.cast<double>();
305 const std::shared_ptr<crocoddyl::ActivationDataAbstract>& data =
306 ✗ activation.createData();
307 const std::shared_ptr<crocoddyl::ActivationDataAbstract>& casted_data =
308 ✗ casted_activation.createData();
309
310 // Compute the activation value
311 Eigen::Vector3d force =
312 ✗ -random_real_in_range(0., 100.) * Eigen::Vector3d::UnitX();
313 ✗ Eigen::VectorXd r = cone.get_A() * force;
314
315 // The last value of the residual is equals to zero since the force is
316 // parallel to the friction cone orientation
317 ✗ BOOST_CHECK_CLOSE(r(nf), 0., 1e-9);
318
319 // The activation value has to be positive since the force is outside the
320 // friction cone
321 ✗ activation.calc(data, r);
322 ✗ BOOST_CHECK(data->a_value > 0.);
323
324 // Checking that casted computation is the same
325 ✗ r = casted_cone.get_A() * force;
326 ✗ BOOST_CHECK_CLOSE(r(nf), 0., 1e-9);
327 ✗ casted_activation.calc(casted_data, r);
328 ✗ BOOST_CHECK(casted_data->a_value > 0.);
329 ✗ }
330
331 ✗ void register_unit_tests() {
332 ✗ framework::master_test_suite().add(
333 ✗ BOOST_TEST_CASE(boost::bind(&test_constructor)));
334 ✗ framework::master_test_suite().add(
335 ✗ BOOST_TEST_CASE(boost::bind(&test_inner_approximation_of_friction_cone)));
336 ✗ framework::master_test_suite().add(
337 ✗ BOOST_TEST_CASE(boost::bind(&test_A_matrix_with_rotation_change)));
338 ✗ framework::master_test_suite().add(
339 ✗ BOOST_TEST_CASE(boost::bind(&test_force_along_friction_cone_normal)));
340 ✗ framework::master_test_suite().add(BOOST_TEST_CASE(
341 boost::bind(&test_negative_force_along_friction_cone_normal)));
342 ✗ framework::master_test_suite().add(BOOST_TEST_CASE(
343 boost::bind(&test_force_parallel_to_friction_cone_normal)));
344 ✗ }
345
346 ✗ bool init_function() {
347 ✗ register_unit_tests();
348 ✗ return true;
349 }
350
351 ✗ int main(int argc, char* argv[]) {
352 ✗ return ::boost::unit_test::unit_test_main(&init_function, argc, argv);
353 }
354