GCC Code Coverage Report


Directory: ./
File: unittest/test_boxqp.cpp
Date: 2025-06-03 08:14:12
Exec Total Coverage
Lines: 0 97 0.0%
Functions: 0 7 0.0%
Branches: 0 506 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