GCC Code Coverage Report


Directory: ./
File: unittest/test_boxqp.cpp
Date: 2025-05-13 10:30:51
Exec Total Coverage
Lines: 0 92 0.0%
Branches: 0 498 0.0%

Line Branch Exec Source
1 ///////////////////////////////////////////////////////////////////////////////
2 // BSD 3-Clause License
3 //
4 // Copyright (C) 2019-2022, University of Edinburgh, Heriot-Watt University
5 // Copyright note valid unless otherwise stated in individual files.
6 // All rights reserved.
7 ///////////////////////////////////////////////////////////////////////////////
8
9 #define BOOST_TEST_NO_MAIN
10 #define BOOST_TEST_ALTERNATIVE_INIT_API
11
12 #include <boost/random.hpp>
13
14 #include "crocoddyl/core/solvers/box-qp.hpp"
15 #include "unittest_common.hpp"
16
17 using namespace boost::unit_test;
18 using namespace crocoddyl::unittest;
19
20 void test_constructor() {
21 // Setup the test
22 std::size_t nx = random_int_in_range(1, 100);
23 crocoddyl::BoxQP boxqp(nx);
24
25 // Test dimension of the decision vector
26 BOOST_CHECK(boxqp.get_nx() == nx);
27 }
28
29 void test_unconstrained_qp_with_identity_hessian() {
30 std::size_t nx = random_int_in_range(2, 5);
31 crocoddyl::BoxQP boxqp(nx);
32 boxqp.set_reg(0.);
33
34 Eigen::MatrixXd hessian = Eigen::MatrixXd::Identity(nx, nx);
35 Eigen::VectorXd gradient = Eigen::VectorXd::Random(nx);
36 Eigen::VectorXd lb =
37 -std::numeric_limits<double>::infinity() * Eigen::VectorXd::Ones(nx);
38 Eigen::VectorXd ub =
39 std::numeric_limits<double>::infinity() * Eigen::VectorXd::Ones(nx);
40 Eigen::VectorXd xinit = Eigen::VectorXd::Random(nx);
41 crocoddyl::BoxQPSolution sol = boxqp.solve(hessian, gradient, lb, ub, xinit);
42
43 // Checking the solution of the problem. Note that it the negative of the
44 // gradient since Hessian is identity matrix
45 BOOST_CHECK((sol.x + gradient).isZero(1e-9));
46
47 // Checking the solution against a regularized case
48 double reg = random_real_in_range(1e-9, 1e2);
49 boxqp.set_reg(reg);
50 crocoddyl::BoxQPSolution sol_reg =
51 boxqp.solve(hessian, gradient, lb, ub, xinit);
52 BOOST_CHECK((sol_reg.x + gradient / (1 + reg)).isZero(1e-9));
53
54 // Checking the all bounds are free and zero clamped
55 BOOST_CHECK(sol.free_idx.size() == nx);
56 BOOST_CHECK(sol.clamped_idx.size() == 0);
57 BOOST_CHECK(sol_reg.free_idx.size() == nx);
58 BOOST_CHECK(sol_reg.clamped_idx.size() == 0);
59 }
60
61 void test_unconstrained_qp() {
62 std::size_t nx = random_int_in_range(2, 5);
63 crocoddyl::BoxQP boxqp(nx);
64 boxqp.set_reg(0.);
65
66 Eigen::MatrixXd H = Eigen::MatrixXd::Random(nx, nx);
67 Eigen::MatrixXd hessian =
68 H.transpose() * H + nx * Eigen::MatrixXd::Identity(nx, nx);
69 hessian = 0.5 * (hessian + hessian.transpose()).eval();
70 Eigen::VectorXd gradient = Eigen::VectorXd::Random(nx);
71 Eigen::VectorXd lb =
72 -std::numeric_limits<double>::infinity() * Eigen::VectorXd::Ones(nx);
73 Eigen::VectorXd ub =
74 std::numeric_limits<double>::infinity() * Eigen::VectorXd::Ones(nx);
75 Eigen::VectorXd xinit = Eigen::VectorXd::Random(nx);
76 crocoddyl::BoxQPSolution sol = boxqp.solve(hessian, gradient, lb, ub, xinit);
77
78 // Checking the solution against the KKT solution
79 Eigen::VectorXd xkkt = -hessian.inverse() * gradient;
80 BOOST_CHECK((sol.x - xkkt).isZero(1e-9));
81
82 // Checking the solution against a regularized KKT problem
83 double reg = random_real_in_range(1e-9, 1e2);
84 boxqp.set_reg(reg);
85 crocoddyl::BoxQPSolution sol_reg =
86 boxqp.solve(hessian, gradient, lb, ub, xinit);
87 Eigen::VectorXd xkkt_reg =
88 -(hessian + reg * Eigen::MatrixXd::Identity(nx, nx)).inverse() * gradient;
89 BOOST_CHECK((sol_reg.x - xkkt_reg).isZero(1e-9));
90
91 // Checking the all bounds are free and zero clamped
92 BOOST_CHECK(sol.free_idx.size() == nx);
93 BOOST_CHECK(sol.clamped_idx.size() == 0);
94 BOOST_CHECK(sol_reg.free_idx.size() == nx);
95 BOOST_CHECK(sol_reg.clamped_idx.size() == 0);
96 }
97
98 void test_box_qp_with_identity_hessian() {
99 std::size_t nx = random_int_in_range(2, 5);
100 crocoddyl::BoxQP boxqp(nx);
101 boxqp.set_reg(0.);
102
103 Eigen::MatrixXd hessian = Eigen::MatrixXd::Identity(nx, nx);
104 Eigen::VectorXd gradient = Eigen::VectorXd::Ones(nx);
105 for (std::size_t i = 0; i < nx; ++i) {
106 gradient(i) *= random_real_in_range(-1., 1.);
107 }
108 Eigen::VectorXd lb = Eigen::VectorXd::Zero(nx);
109 Eigen::VectorXd ub = Eigen::VectorXd::Ones(nx);
110 Eigen::VectorXd xinit = Eigen::VectorXd::Random(nx);
111 crocoddyl::BoxQPSolution sol = boxqp.solve(hessian, gradient, lb, ub, xinit);
112
113 // The analytical solution is the a bounded, and negative, gradient
114 Eigen::VectorXd negbounded_gradient(nx), negbounded_gradient_reg(nx);
115 std::size_t nf = nx, nc = 0, nf_reg = nx, nc_reg = 0;
116 double reg = random_real_in_range(1e-9, 1e2);
117 for (std::size_t i = 0; i < nx; ++i) {
118 negbounded_gradient(i) = std::max(std::min(-gradient(i), ub(i)), lb(i));
119 negbounded_gradient_reg(i) =
120 std::max(std::min(-gradient(i) / (1 + reg), ub(i)), lb(i));
121 if (negbounded_gradient(i) != -gradient(i)) {
122 nc += 1;
123 nf -= 1;
124 }
125 if (negbounded_gradient_reg(i) != -gradient(i) / (1 + reg)) {
126 nc_reg += 1;
127 nf_reg -= 1;
128 }
129 }
130
131 // Checking the solution of the problem. Note that it the negative of the
132 // gradient since Hessian is identity matrix
133 BOOST_CHECK((sol.x - negbounded_gradient).isZero(1e-9));
134
135 // Checking the solution against a regularized case
136 boxqp.set_reg(reg);
137 crocoddyl::BoxQPSolution sol_reg =
138 boxqp.solve(hessian, gradient, lb, ub, xinit);
139 BOOST_CHECK((sol_reg.x - negbounded_gradient_reg).isZero(1e-9));
140
141 // Checking the all bounds are free and zero clamped
142 BOOST_CHECK(sol.free_idx.size() == nf);
143 BOOST_CHECK(sol.clamped_idx.size() == nc);
144 BOOST_CHECK(sol_reg.free_idx.size() == nf_reg);
145 BOOST_CHECK(sol_reg.clamped_idx.size() == nc_reg);
146 }
147
148 void register_unit_tests() {
149 framework::master_test_suite().add(
150 BOOST_TEST_CASE(boost::bind(&test_constructor)));
151 framework::master_test_suite().add(BOOST_TEST_CASE(
152 boost::bind(&test_unconstrained_qp_with_identity_hessian)));
153 framework::master_test_suite().add(
154 BOOST_TEST_CASE(boost::bind(&test_unconstrained_qp)));
155 framework::master_test_suite().add(
156 BOOST_TEST_CASE(boost::bind(&test_box_qp_with_identity_hessian)));
157 }
158
159 bool init_function() {
160 register_unit_tests();
161 return true;
162 }
163
164 int main(int argc, char* argv[]) {
165 return ::boost::unit_test::unit_test_main(&init_function, argc, argv);
166 }
167