GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: unittest/test_cost_sum.cpp Lines: 293 293 100.0 %
Date: 2024-02-13 11:12:33 Branches: 627 1230 51.0 %

Line Branch Exec Source
1
///////////////////////////////////////////////////////////////////////////////
2
// BSD 3-Clause License
3
//
4
// Copyright (C) 2019-2023, 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 "crocoddyl/core/actions/lqr.hpp"
13
#include "crocoddyl/multibody/data/multibody.hpp"
14
#include "factory/cost.hpp"
15
#include "unittest_common.hpp"
16
17
using namespace boost::unit_test;
18
using namespace crocoddyl::unittest;
19
20
//----------------------------------------------------------------------------//
21
22
3
void test_constructor(StateModelTypes::Type state_type) {
23
  // Setup the test
24
6
  StateModelFactory state_factory;
25

6
  crocoddyl::CostModelSum model(state_factory.create(state_type));
26
27
  // Run the print function
28
6
  std::ostringstream tmp;
29
3
  tmp << model;
30
31
  // Test the initial size of the map
32



3
  BOOST_CHECK(model.get_costs().size() == 0);
33
3
}
34
35
3
void test_addCost(StateModelTypes::Type state_type) {
36
  // Setup the test
37
6
  StateModelFactory state_factory;
38

6
  crocoddyl::CostModelSum model(state_factory.create(state_type));
39
40
  // add an active cost
41
  boost::shared_ptr<crocoddyl::CostModelAbstract> rand_cost_1 =
42
6
      create_random_cost(state_type);
43

3
  model.addCost("random_cost_1", rand_cost_1, 1.);
44



3
  BOOST_CHECK(model.get_nr() == rand_cost_1->get_activation()->get_nr());
45



3
  BOOST_CHECK(model.get_nr_total() == rand_cost_1->get_activation()->get_nr());
46
47
  // add an inactive cost
48
  boost::shared_ptr<crocoddyl::CostModelAbstract> rand_cost_2 =
49
6
      create_random_cost(state_type);
50

3
  model.addCost("random_cost_2", rand_cost_2, 1., false);
51



3
  BOOST_CHECK(model.get_nr() == rand_cost_1->get_activation()->get_nr());
52



3
  BOOST_CHECK(model.get_nr_total() ==
53
              rand_cost_1->get_activation()->get_nr() +
54
                  rand_cost_2->get_activation()->get_nr());
55
56
  // change the random cost 2 status
57

3
  model.changeCostStatus("random_cost_2", true);
58



3
  BOOST_CHECK(model.get_nr() == rand_cost_1->get_activation()->get_nr() +
59
                                    rand_cost_2->get_activation()->get_nr());
60



3
  BOOST_CHECK(model.get_nr_total() ==
61
              rand_cost_1->get_activation()->get_nr() +
62
                  rand_cost_2->get_activation()->get_nr());
63
64
  // change the random cost 1 status
65

3
  model.changeCostStatus("random_cost_1", false);
66



3
  BOOST_CHECK(model.get_nr() == rand_cost_2->get_activation()->get_nr());
67



3
  BOOST_CHECK(model.get_nr_total() ==
68
              rand_cost_1->get_activation()->get_nr() +
69
                  rand_cost_2->get_activation()->get_nr());
70
3
}
71
72
3
void test_addCost_error_message(StateModelTypes::Type state_type) {
73
  // Setup the test
74
6
  StateModelFactory state_factory;
75

6
  crocoddyl::CostModelSum model(state_factory.create(state_type));
76
77
  // create an cost object
78
  boost::shared_ptr<crocoddyl::CostModelAbstract> rand_cost =
79
6
      create_random_cost(state_type);
80
81
  // add twice the same cost object to the container
82

3
  model.addCost("random_cost", rand_cost, 1.);
83
84
  // test error message when we add a duplicate cost
85
6
  CaptureIOStream capture_ios;
86
3
  capture_ios.beginCapture();
87

3
  model.addCost("random_cost", rand_cost, 1.);
88
3
  capture_ios.endCapture();
89
6
  std::stringstream expected_buffer;
90
  expected_buffer << "Warning: we couldn't add the random_cost cost item, it "
91
3
                     "already existed."
92
3
                  << std::endl;
93




3
  BOOST_CHECK(capture_ios.str() == expected_buffer.str());
94
95
  // test error message when we change the cost status of an inexistent cost
96
3
  capture_ios.beginCapture();
97

3
  model.changeCostStatus("no_exist_cost", true);
98
3
  capture_ios.endCapture();
99
3
  expected_buffer.clear();
100
  expected_buffer << "Warning: we couldn't change the status of the "
101
3
                     "no_exist_cost cost item, it doesn't exist."
102
3
                  << std::endl;
103




3
  BOOST_CHECK(capture_ios.str() == expected_buffer.str());
104
3
}
105
106
3
void test_removeCost(StateModelTypes::Type state_type) {
107
  // Setup the test
108
6
  StateModelFactory state_factory;
109

6
  crocoddyl::CostModelSum model(state_factory.create(state_type));
110
111
  // add an active cost
112
  boost::shared_ptr<crocoddyl::CostModelAbstract> rand_cost =
113
6
      create_random_cost(state_type);
114

3
  model.addCost("random_cost", rand_cost, 1.);
115



3
  BOOST_CHECK(model.get_nr() == rand_cost->get_activation()->get_nr());
116
117
  // remove the cost
118

3
  model.removeCost("random_cost");
119



3
  BOOST_CHECK(model.get_nr() == 0);
120



3
  BOOST_CHECK(model.get_nr_total() == 0);
121
3
}
122
123
3
void test_removeCost_error_message(StateModelTypes::Type state_type) {
124
  // Setup the test
125
6
  StateModelFactory state_factory;
126

6
  crocoddyl::CostModelSum model(state_factory.create(state_type));
127
128
  // remove a none existing cost form the container, we expect a cout message
129
  // here
130
6
  CaptureIOStream capture_ios;
131
3
  capture_ios.beginCapture();
132

3
  model.removeCost("random_cost");
133
3
  capture_ios.endCapture();
134
135
  // Test that the error message is sent.
136
6
  std::stringstream expected_buffer;
137
  expected_buffer << "Warning: we couldn't remove the random_cost cost item, "
138
3
                     "it doesn't exist."
139
3
                  << std::endl;
140




3
  BOOST_CHECK(capture_ios.str() == expected_buffer.str());
141
3
}
142
143
3
void test_calc(StateModelTypes::Type state_type) {
144
  // setup the test
145
6
  StateModelFactory state_factory;
146

6
  crocoddyl::CostModelSum model(state_factory.create(state_type));
147
  // create the corresponding data object
148
  const boost::shared_ptr<crocoddyl::StateMultibody>& state =
149
6
      boost::static_pointer_cast<crocoddyl::StateMultibody>(model.get_state());
150
3
  pinocchio::Model& pinocchio_model = *state->get_pinocchio().get();
151
6
  pinocchio::Data pinocchio_data(pinocchio_model);
152
6
  crocoddyl::DataCollectorMultibody shared_data(&pinocchio_data);
153
154
  // create and add some cost objects
155
6
  std::vector<boost::shared_ptr<crocoddyl::CostModelAbstract> > models;
156
6
  std::vector<boost::shared_ptr<crocoddyl::CostDataAbstract> > datas;
157
18
  for (std::size_t i = 0; i < 5; ++i) {
158
30
    std::ostringstream os;
159

15
    os << "random_cost_" << i;
160
    const boost::shared_ptr<crocoddyl::CostModelAbstract>& m =
161
15
        create_random_cost(state_type);
162

15
    model.addCost(os.str(), m, 1.);
163
15
    models.push_back(m);
164

15
    datas.push_back(m->createData(&shared_data));
165
  }
166
167
  // create the data of the cost sum
168
  const boost::shared_ptr<crocoddyl::CostDataSum>& data =
169
6
      model.createData(&shared_data);
170
171
  // compute the cost sum data for the case when all costs are defined as active
172
6
  const Eigen::VectorXd x1 = state->rand();
173

6
  const Eigen::VectorXd u1 = Eigen::VectorXd::Random(model.get_nu());
174

3
  crocoddyl::unittest::updateAllPinocchio(&pinocchio_model, &pinocchio_data,
175
                                          x1);
176

3
  model.calc(data, x1, u1);
177
178
  // check that the cost has been filled
179



3
  BOOST_CHECK(data->cost > 0.);
180
181
  // check the cost against single cost computations
182
3
  double cost = 0;
183
18
  for (std::size_t i = 0; i < 5; ++i) {
184

15
    models[i]->calc(datas[i], x1, u1);
185
15
    cost += datas[i]->cost;
186
  }
187



3
  BOOST_CHECK(data->cost == cost);
188
189
  // compute the cost sum data for the case when the first three costs are
190
  // defined as active
191

3
  model.changeCostStatus("random_cost_3", false);
192

3
  model.changeCostStatus("random_cost_4", false);
193
6
  const Eigen::VectorXd x2 = state->rand();
194

6
  const Eigen::VectorXd u2 = Eigen::VectorXd::Random(model.get_nu());
195

3
  crocoddyl::unittest::updateAllPinocchio(&pinocchio_model, &pinocchio_data,
196
                                          x1);
197

3
  model.calc(data, x2, u2);
198
3
  cost = 0;
199
12
  for (std::size_t i = 0; i < 3;
200
       ++i) {  // we need to update data because this costs are active
201

9
    models[i]->calc(datas[i], x2, u2);
202
9
    cost += datas[i]->cost;
203
  }
204



3
  BOOST_CHECK(data->cost == cost);
205
3
}
206
207
3
void test_calcDiff(StateModelTypes::Type state_type) {
208
  // setup the test
209
6
  StateModelFactory state_factory;
210

6
  crocoddyl::CostModelSum model(state_factory.create(state_type));
211
  // create the corresponding data object
212
  const boost::shared_ptr<crocoddyl::StateMultibody>& state =
213
6
      boost::static_pointer_cast<crocoddyl::StateMultibody>(model.get_state());
214
3
  pinocchio::Model& pinocchio_model = *state->get_pinocchio().get();
215
6
  pinocchio::Data pinocchio_data(pinocchio_model);
216
6
  crocoddyl::DataCollectorMultibody shared_data(&pinocchio_data);
217
218
  // create and add some cost objects
219
6
  std::vector<boost::shared_ptr<crocoddyl::CostModelAbstract> > models;
220
6
  std::vector<boost::shared_ptr<crocoddyl::CostDataAbstract> > datas;
221
18
  for (std::size_t i = 0; i < 5; ++i) {
222
30
    std::ostringstream os;
223

15
    os << "random_cost_" << i;
224
    const boost::shared_ptr<crocoddyl::CostModelAbstract>& m =
225
15
        create_random_cost(state_type);
226

15
    model.addCost(os.str(), m, 1.);
227
15
    models.push_back(m);
228

15
    datas.push_back(m->createData(&shared_data));
229
  }
230
231
  // create the data of the cost sum
232
  const boost::shared_ptr<crocoddyl::CostDataSum>& data =
233
6
      model.createData(&shared_data);
234
235
  // compute the cost sum data for the case when all costs are defined as active
236
6
  Eigen::VectorXd x1 = state->rand();
237

6
  const Eigen::VectorXd u1 = Eigen::VectorXd::Random(model.get_nu());
238

3
  crocoddyl::unittest::updateAllPinocchio(&pinocchio_model, &pinocchio_data,
239
                                          x1);
240

3
  model.calc(data, x1, u1);
241

3
  model.calcDiff(data, x1, u1);
242
243
  // check that the cost has been filled
244



3
  BOOST_CHECK(data->cost > 0.);
245
246
  // check the cost against single cost computations
247
3
  double cost = 0;
248

6
  Eigen::VectorXd Lx = Eigen::VectorXd::Zero(state->get_ndx());
249

6
  Eigen::VectorXd Lu = Eigen::VectorXd::Zero(model.get_nu());
250
  Eigen::MatrixXd Lxx =
251

6
      Eigen::MatrixXd::Zero(state->get_ndx(), state->get_ndx());
252

6
  Eigen::MatrixXd Lxu = Eigen::MatrixXd::Zero(state->get_ndx(), model.get_nu());
253

6
  Eigen::MatrixXd Luu = Eigen::MatrixXd::Zero(model.get_nu(), model.get_nu());
254
18
  for (std::size_t i = 0; i < 5; ++i) {
255

15
    models[i]->calc(datas[i], x1, u1);
256

15
    models[i]->calcDiff(datas[i], x1, u1);
257
15
    cost += datas[i]->cost;
258
15
    Lx += datas[i]->Lx;
259
15
    Lu += datas[i]->Lu;
260
15
    Lxx += datas[i]->Lxx;
261
15
    Lxu += datas[i]->Lxu;
262
15
    Luu += datas[i]->Luu;
263
  }
264



3
  BOOST_CHECK(data->cost == cost);
265



3
  BOOST_CHECK(data->Lx == Lx);
266



3
  BOOST_CHECK(data->Lu == Lu);
267



3
  BOOST_CHECK(data->Lxx == Lxx);
268



3
  BOOST_CHECK(data->Lxu == Lxu);
269



3
  BOOST_CHECK(data->Luu == Luu);
270
271
3
  x1 = state->rand();
272

3
  crocoddyl::unittest::updateAllPinocchio(&pinocchio_model, &pinocchio_data,
273
                                          x1);
274

3
  model.calc(data, x1);
275

3
  model.calcDiff(data, x1);
276
3
  cost = 0.;
277
3
  Lx.setZero();
278
3
  Lxx.setZero();
279
18
  for (std::size_t i = 0; i < 5; ++i) {
280

15
    models[i]->calc(datas[i], x1);
281

15
    models[i]->calcDiff(datas[i], x1);
282
15
    cost += datas[i]->cost;
283
15
    Lx += datas[i]->Lx;
284
15
    Lxx += datas[i]->Lxx;
285
  }
286



3
  BOOST_CHECK(data->cost == cost);
287



3
  BOOST_CHECK(data->Lx == Lx);
288



3
  BOOST_CHECK(data->Lxx == Lxx);
289
290
  // compute the cost sum data for the case when the first three costs are
291
  // defined as active
292

3
  model.changeCostStatus("random_cost_3", false);
293

3
  model.changeCostStatus("random_cost_4", false);
294
6
  Eigen::VectorXd x2 = state->rand();
295

6
  const Eigen::VectorXd u2 = Eigen::VectorXd::Random(model.get_nu());
296

3
  crocoddyl::unittest::updateAllPinocchio(&pinocchio_model, &pinocchio_data,
297
                                          x2);
298

3
  model.calc(data, x2, u2);
299

3
  model.calcDiff(data, x2, u2);
300
3
  cost = 0;
301
3
  Lx.setZero();
302
3
  Lu.setZero();
303
3
  Lxx.setZero();
304
3
  Lxu.setZero();
305
3
  Luu.setZero();
306
12
  for (std::size_t i = 0; i < 3;
307
       ++i) {  // we need to update data because this costs are active
308

9
    models[i]->calc(datas[i], x2, u2);
309

9
    models[i]->calcDiff(datas[i], x2, u2);
310
9
    cost += datas[i]->cost;
311
9
    Lx += datas[i]->Lx;
312
9
    Lu += datas[i]->Lu;
313
9
    Lxx += datas[i]->Lxx;
314
9
    Lxu += datas[i]->Lxu;
315
9
    Luu += datas[i]->Luu;
316
  }
317



3
  BOOST_CHECK(data->cost == cost);
318



3
  BOOST_CHECK(data->Lx == Lx);
319



3
  BOOST_CHECK(data->Lu == Lu);
320



3
  BOOST_CHECK(data->Lxx == Lxx);
321



3
  BOOST_CHECK(data->Lxu == Lxu);
322



3
  BOOST_CHECK(data->Luu == Luu);
323
324
3
  x2 = state->rand();
325

3
  crocoddyl::unittest::updateAllPinocchio(&pinocchio_model, &pinocchio_data,
326
                                          x2);
327

3
  model.calc(data, x2);
328

3
  model.calcDiff(data, x2);
329
3
  cost = 0.;
330
3
  Lx.setZero();
331
3
  Lxx.setZero();
332
12
  for (std::size_t i = 0; i < 3; ++i) {
333

9
    models[i]->calc(datas[i], x2);
334

9
    models[i]->calcDiff(datas[i], x2);
335
9
    cost += datas[i]->cost;
336
9
    Lx += datas[i]->Lx;
337
9
    Lxx += datas[i]->Lxx;
338
  }
339



3
  BOOST_CHECK(data->cost == cost);
340



3
  BOOST_CHECK(data->Lx == Lx);
341



3
  BOOST_CHECK(data->Lxx == Lxx);
342
3
}
343
344
3
void test_get_costs(StateModelTypes::Type state_type) {
345
  // setup the test
346
6
  StateModelFactory state_factory;
347

6
  crocoddyl::CostModelSum model(state_factory.create(state_type));
348
  // create the corresponding data object
349
  const boost::shared_ptr<crocoddyl::StateMultibody>& state =
350
6
      boost::static_pointer_cast<crocoddyl::StateMultibody>(model.get_state());
351
6
  pinocchio::Data pinocchio_data(*state->get_pinocchio().get());
352
353
  // create and add some contact objects
354
18
  for (unsigned i = 0; i < 5; ++i) {
355
15
    std::ostringstream os;
356

15
    os << "random_cost_" << i;
357

15
    model.addCost(os.str(), create_random_cost(state_type), 1.);
358
  }
359
360
  // get the contacts
361
3
  const crocoddyl::CostModelSum::CostModelContainer& costs = model.get_costs();
362
363
  // test
364
3
  crocoddyl::CostModelSum::CostModelContainer::const_iterator it_m, end_m;
365
  unsigned i;
366
18
  for (i = 0, it_m = costs.begin(), end_m = costs.end(); it_m != end_m;
367
15
       ++it_m, ++i) {
368
30
    std::ostringstream os;
369

15
    os << "random_cost_" << i;
370



15
    BOOST_CHECK(it_m->first == os.str());
371
  }
372
3
}
373
374
3
void test_get_nr(StateModelTypes::Type state_type) {
375
  // Setup the test
376
6
  StateModelFactory state_factory;
377

6
  crocoddyl::CostModelSum model(state_factory.create(state_type));
378
379
  // create the corresponding data object
380
  const boost::shared_ptr<crocoddyl::StateMultibody>& state =
381
6
      boost::static_pointer_cast<crocoddyl::StateMultibody>(model.get_state());
382
6
  pinocchio::Data pinocchio_data(*state->get_pinocchio().get());
383
384
  // create and add some contact objects
385
18
  for (unsigned i = 0; i < 5; ++i) {
386
15
    std::ostringstream os;
387

15
    os << "random_cost_" << i;
388

15
    model.addCost(os.str(), create_random_cost(state_type), 1.);
389
  }
390
391
  // compute ni
392
3
  std::size_t nr = 0;
393
3
  crocoddyl::CostModelSum::CostModelContainer::const_iterator it_m, end_m;
394
18
  for (it_m = model.get_costs().begin(), end_m = model.get_costs().end();
395
18
       it_m != end_m; ++it_m) {
396
15
    nr += it_m->second->cost->get_activation()->get_nr();
397
  }
398
399



3
  BOOST_CHECK(nr == model.get_nr());
400
3
}
401
402
3
void test_shareMemory(StateModelTypes::Type state_type) {
403
  // setup the test
404
6
  StateModelFactory state_factory;
405
  const boost::shared_ptr<crocoddyl::StateAbstract> state =
406
6
      state_factory.create(state_type);
407
6
  crocoddyl::CostModelSum cost_model(state);
408
6
  crocoddyl::DataCollectorAbstract shared_data;
409
  const boost::shared_ptr<crocoddyl::CostDataSum>& cost_data =
410
6
      cost_model.createData(&shared_data);
411
412
3
  const std::size_t ndx = state->get_ndx();
413
3
  const std::size_t nu = cost_model.get_nu();
414
6
  crocoddyl::ActionModelLQR action_model(ndx, nu);
415
  const boost::shared_ptr<crocoddyl::ActionDataAbstract>& action_data =
416
6
      action_model.createData();
417
418
3
  cost_data->shareMemory(action_data.get());
419

3
  cost_data->Lx = Eigen::VectorXd::Random(ndx);
420

3
  cost_data->Lu = Eigen::VectorXd::Random(nu);
421

3
  cost_data->Lxx = Eigen::MatrixXd::Random(ndx, ndx);
422

3
  cost_data->Luu = Eigen::MatrixXd::Random(nu, nu);
423

3
  cost_data->Lxu = Eigen::MatrixXd::Random(ndx, nu);
424
425
  // check that the data has been shared
426



3
  BOOST_CHECK(action_data->Lx.isApprox(cost_data->Lx, 1e-9));
427



3
  BOOST_CHECK(action_data->Lu.isApprox(cost_data->Lu, 1e-9));
428



3
  BOOST_CHECK(action_data->Lxx.isApprox(cost_data->Lxx, 1e-9));
429



3
  BOOST_CHECK(action_data->Luu.isApprox(cost_data->Luu, 1e-9));
430



3
  BOOST_CHECK(action_data->Lxu.isApprox(cost_data->Lxu, 1e-9));
431
3
}
432
433
//----------------------------------------------------------------------------//
434
435
3
void register_unit_tests(StateModelTypes::Type state_type) {
436

6
  boost::test_tools::output_test_stream test_name;
437
  test_name << "test_CostModelSum"
438

3
            << "_" << state_type;
439


3
  std::cout << "Running " << test_name.str() << std::endl;
440


3
  test_suite* ts = BOOST_TEST_SUITE(test_name.str());
441


3
  ts->add(BOOST_TEST_CASE(boost::bind(&test_constructor, state_type)));
442


3
  ts->add(BOOST_TEST_CASE(boost::bind(&test_addCost, state_type)));
443
3
  ts->add(
444


3
      BOOST_TEST_CASE(boost::bind(&test_addCost_error_message, state_type)));
445


3
  ts->add(BOOST_TEST_CASE(boost::bind(&test_removeCost, state_type)));
446
3
  ts->add(
447


3
      BOOST_TEST_CASE(boost::bind(&test_removeCost_error_message, state_type)));
448


3
  ts->add(BOOST_TEST_CASE(boost::bind(&test_calc, state_type)));
449


3
  ts->add(BOOST_TEST_CASE(boost::bind(&test_calcDiff, state_type)));
450


3
  ts->add(BOOST_TEST_CASE(boost::bind(&test_get_costs, state_type)));
451


3
  ts->add(BOOST_TEST_CASE(boost::bind(&test_get_nr, state_type)));
452


3
  ts->add(BOOST_TEST_CASE(boost::bind(&test_shareMemory, state_type)));
453

3
  framework::master_test_suite().add(ts);
454
3
}
455
456
1
bool init_function() {
457
1
  register_unit_tests(StateModelTypes::StateMultibody_TalosArm);
458
1
  register_unit_tests(StateModelTypes::StateMultibody_HyQ);
459
1
  register_unit_tests(StateModelTypes::StateMultibody_Talos);
460
1
  return true;
461
}
462
463
1
int main(int argc, char** argv) {
464
1
  return ::boost::unit_test::unit_test_main(&init_function, argc, argv);
465
}