GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: unittest/test_boxqp.cpp Lines: 97 97 100.0 %
Date: 2024-02-13 11:12:33 Branches: 253 498 50.8 %

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
1
void test_constructor() {
21
  // Setup the test
22
1
  std::size_t nx = random_int_in_range(1, 100);
23
2
  crocoddyl::BoxQP boxqp(nx);
24
25
  // Test dimension of the decision vector
26



1
  BOOST_CHECK(boxqp.get_nx() == nx);
27
1
}
28
29
1
void test_unconstrained_qp_with_identity_hessian() {
30
1
  std::size_t nx = random_int_in_range(2, 5);
31
2
  crocoddyl::BoxQP boxqp(nx);
32
1
  boxqp.set_reg(0.);
33
34

2
  Eigen::MatrixXd hessian = Eigen::MatrixXd::Identity(nx, nx);
35

2
  Eigen::VectorXd gradient = Eigen::VectorXd::Random(nx);
36
  Eigen::VectorXd lb =
37

2
      -std::numeric_limits<double>::infinity() * Eigen::VectorXd::Ones(nx);
38
  Eigen::VectorXd ub =
39

2
      std::numeric_limits<double>::infinity() * Eigen::VectorXd::Ones(nx);
40

2
  Eigen::VectorXd xinit = Eigen::VectorXd::Random(nx);
41

2
  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




1
  BOOST_CHECK((sol.x + gradient).isZero(1e-9));
46
47
  // Checking the solution against a regularized case
48
1
  double reg = random_real_in_range(1e-9, 1e2);
49
1
  boxqp.set_reg(reg);
50
  crocoddyl::BoxQPSolution sol_reg =
51

2
      boxqp.solve(hessian, gradient, lb, ub, xinit);
52




1
  BOOST_CHECK((sol_reg.x + gradient / (1 + reg)).isZero(1e-9));
53
54
  // Checking the all bounds are free and zero clamped
55



1
  BOOST_CHECK(sol.free_idx.size() == nx);
56



1
  BOOST_CHECK(sol.clamped_idx.size() == 0);
57



1
  BOOST_CHECK(sol_reg.free_idx.size() == nx);
58



1
  BOOST_CHECK(sol_reg.clamped_idx.size() == 0);
59
1
}
60
61
1
void test_unconstrained_qp() {
62
1
  std::size_t nx = random_int_in_range(2, 5);
63
2
  crocoddyl::BoxQP boxqp(nx);
64
1
  boxqp.set_reg(0.);
65
66

2
  Eigen::MatrixXd H = Eigen::MatrixXd::Random(nx, nx);
67
  Eigen::MatrixXd hessian =
68



2
      H.transpose() * H + nx * Eigen::MatrixXd::Identity(nx, nx);
69


1
  hessian = 0.5 * (hessian + hessian.transpose()).eval();
70

2
  Eigen::VectorXd gradient = Eigen::VectorXd::Random(nx);
71
  Eigen::VectorXd lb =
72

2
      -std::numeric_limits<double>::infinity() * Eigen::VectorXd::Ones(nx);
73
  Eigen::VectorXd ub =
74

2
      std::numeric_limits<double>::infinity() * Eigen::VectorXd::Ones(nx);
75

2
  Eigen::VectorXd xinit = Eigen::VectorXd::Random(nx);
76

2
  crocoddyl::BoxQPSolution sol = boxqp.solve(hessian, gradient, lb, ub, xinit);
77
78
  // Checking the solution against the KKT solution
79


2
  Eigen::VectorXd xkkt = -hessian.inverse() * gradient;
80




1
  BOOST_CHECK((sol.x - xkkt).isZero(1e-9));
81
82
  // Checking the solution against a regularized KKT problem
83
1
  double reg = random_real_in_range(1e-9, 1e2);
84
1
  boxqp.set_reg(reg);
85
  crocoddyl::BoxQPSolution sol_reg =
86

2
      boxqp.solve(hessian, gradient, lb, ub, xinit);
87
  Eigen::VectorXd xkkt_reg =
88



2
      -(hessian + reg * Eigen::MatrixXd::Identity(nx, nx)).inverse() * gradient;
89




1
  BOOST_CHECK((sol_reg.x - xkkt_reg).isZero(1e-9));
90
91
  // Checking the all bounds are free and zero clamped
92



1
  BOOST_CHECK(sol.free_idx.size() == nx);
93



1
  BOOST_CHECK(sol.clamped_idx.size() == 0);
94



1
  BOOST_CHECK(sol_reg.free_idx.size() == nx);
95



1
  BOOST_CHECK(sol_reg.clamped_idx.size() == 0);
96
1
}
97
98
1
void test_box_qp_with_identity_hessian() {
99
1
  std::size_t nx = random_int_in_range(2, 5);
100
2
  crocoddyl::BoxQP boxqp(nx);
101
1
  boxqp.set_reg(0.);
102
103

2
  Eigen::MatrixXd hessian = Eigen::MatrixXd::Identity(nx, nx);
104

2
  Eigen::VectorXd gradient = Eigen::VectorXd::Ones(nx);
105
3
  for (std::size_t i = 0; i < nx; ++i) {
106

2
    gradient(i) *= random_real_in_range(-1., 1.);
107
  }
108

2
  Eigen::VectorXd lb = Eigen::VectorXd::Zero(nx);
109

2
  Eigen::VectorXd ub = Eigen::VectorXd::Ones(nx);
110

2
  Eigen::VectorXd xinit = Eigen::VectorXd::Random(nx);
111

2
  crocoddyl::BoxQPSolution sol = boxqp.solve(hessian, gradient, lb, ub, xinit);
112
113
  // The analytical solution is the a bounded, and negative, gradient
114

2
  Eigen::VectorXd negbounded_gradient(nx), negbounded_gradient_reg(nx);
115
1
  std::size_t nf = nx, nc = 0, nf_reg = nx, nc_reg = 0;
116
1
  double reg = random_real_in_range(1e-9, 1e2);
117
3
  for (std::size_t i = 0; i < nx; ++i) {
118


2
    negbounded_gradient(i) = std::max(std::min(-gradient(i), ub(i)), lb(i));
119
2
    negbounded_gradient_reg(i) =
120

2
        std::max(std::min(-gradient(i) / (1 + reg), ub(i)), lb(i));
121

2
    if (negbounded_gradient(i) != -gradient(i)) {
122
1
      nc += 1;
123
1
      nf -= 1;
124
    }
125

2
    if (negbounded_gradient_reg(i) != -gradient(i) / (1 + reg)) {
126
1
      nc_reg += 1;
127
1
      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




1
  BOOST_CHECK((sol.x - negbounded_gradient).isZero(1e-9));
134
135
  // Checking the solution against a regularized case
136
1
  boxqp.set_reg(reg);
137
  crocoddyl::BoxQPSolution sol_reg =
138

2
      boxqp.solve(hessian, gradient, lb, ub, xinit);
139




1
  BOOST_CHECK((sol_reg.x - negbounded_gradient_reg).isZero(1e-9));
140
141
  // Checking the all bounds are free and zero clamped
142



1
  BOOST_CHECK(sol.free_idx.size() == nf);
143



1
  BOOST_CHECK(sol.clamped_idx.size() == nc);
144



1
  BOOST_CHECK(sol_reg.free_idx.size() == nf_reg);
145



1
  BOOST_CHECK(sol_reg.clamped_idx.size() == nc_reg);
146
1
}
147
148
1
void register_unit_tests() {
149
1
  framework::master_test_suite().add(
150


1
      BOOST_TEST_CASE(boost::bind(&test_constructor)));
151


1
  framework::master_test_suite().add(BOOST_TEST_CASE(
152
      boost::bind(&test_unconstrained_qp_with_identity_hessian)));
153
1
  framework::master_test_suite().add(
154


1
      BOOST_TEST_CASE(boost::bind(&test_unconstrained_qp)));
155
1
  framework::master_test_suite().add(
156


1
      BOOST_TEST_CASE(boost::bind(&test_box_qp_with_identity_hessian)));
157
1
}
158
159
1
bool init_function() {
160
1
  register_unit_tests();
161
1
  return true;
162
}
163
164
1
int main(int argc, char* argv[]) {
165
1
  return ::boost::unit_test::unit_test_main(&init_function, argc, argv);
166
}