moveit2
The MoveIt Motion Planning Framework for ROS 2.
test_cost_functions.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2023, PickNik Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of PickNik Inc. nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
39 #include <gtest/gtest.h>
41 
42 constexpr size_t TIMESTEPS = 100;
43 constexpr size_t VARIABLES = 6;
44 constexpr double PENALTY = 1.0;
45 
46 TEST(NoiseGeneratorTest, testGetCostFunctionAllValidStates)
47 {
48  // GIVEN a cost function with a state validator that only returns valid costs of 0.0
49  auto state_validator_fn = [](const Eigen::VectorXd& /* state_positions */) { return 0.0; };
50  auto cost_fn =
51  stomp_moveit::costs::getCostFunctionFromStateValidator(state_validator_fn, 0.1 /* interpolation_step_size */);
52 
53  // GIVEN a trajectory with TIMESTEPS states, with waypoints interpolating from 0.0 to 1.0 joint values
54  Eigen::MatrixXd values = Eigen::MatrixXd::Zero(VARIABLES, TIMESTEPS);
55  const int last_timestep = values.cols() - 1;
56  for (int timestep = 0; timestep <= last_timestep; ++timestep)
57  {
58  values.col(timestep).fill(static_cast<double>(timestep) / last_timestep);
59  }
60 
61  // WHEN the cost function is applied to the trajectory
62  Eigen::VectorXd costs(TIMESTEPS);
63  bool validity;
64  ASSERT_TRUE(cost_fn(values, costs, validity));
65 
66  // THEN the trajectory must be valid and have zero costs
67  EXPECT_TRUE(validity);
68  EXPECT_EQ(costs.sum(), 0.0);
69 }
70 
71 TEST(NoiseGeneratorTest, testGetCostFunctionInvalidStates)
72 {
73  // GIVEN a cost function with a simulated state validator that tags selected timesteps as invalid.
74  // The state validation function is called once per timestep since interpolation is disabled.
75  // This assumption is confirmed as boundary assumption after calling the solver.
76  static const std::set<int> INVALID_TIMESTEPS(
77  { 0, 10, 11, 12, 25, 26, 27, 46, 63, 64, 65, 66, 67, 68, 69, 97, 98, 99 });
78  size_t timestep_counter = 0;
79  auto state_validator_fn = [&](const Eigen::VectorXd& /* state_positions */) {
80  return PENALTY * INVALID_TIMESTEPS.count(timestep_counter++);
81  };
82  auto cost_fn =
83  stomp_moveit::costs::getCostFunctionFromStateValidator(state_validator_fn, 0.0 /* interpolation disabled */);
84 
85  // GIVEN a trajectory with TIMESTEPS states, with waypoints interpolating from 0.0 to 1.0 joint values
86  Eigen::MatrixXd values = Eigen::MatrixXd::Zero(VARIABLES, TIMESTEPS);
87  const int last_timestep = values.cols() - 1;
88  for (int timestep = 0; timestep <= last_timestep; ++timestep)
89  {
90  values.col(timestep).fill(static_cast<double>(timestep) / last_timestep);
91  }
92 
93  // WHEN the cost function is applied to the trajectory
94  Eigen::VectorXd costs(TIMESTEPS);
95  bool validity;
96  ASSERT_TRUE(cost_fn(values, costs, validity));
97 
98  // THEN the following boundary assumptions about cost function outputs, costs and validity need to be met
99  EXPECT_FALSE(validity); // invalid states must result in an invalid trajectory
100  EXPECT_EQ(timestep_counter, 100u); // 100 timesteps checked without interpolation
101  EXPECT_LE(costs.maxCoeff(), PENALTY); // the highest cost must not be higher than the configured penalty
102  EXPECT_GE(costs.minCoeff(), 0.0); // no negative cost values should be computed
103 
104  // THEN the total cost must equal the sum of penalties produced by all invalid timesteps
105  EXPECT_DOUBLE_EQ(costs.sum(), PENALTY * INVALID_TIMESTEPS.size());
106 
107  // THEN invalid timesteps must account for the majority of the total cost.
108  // We expect that invalid windows cover at least 2*sigma (=68.1%) of each cost distribution.
109  const std::vector<int> invalid_timesteps_vec(INVALID_TIMESTEPS.begin(), INVALID_TIMESTEPS.end());
110  EXPECT_GE(costs(invalid_timesteps_vec).sum(), 0.681 * PENALTY);
111 }
112 
113 int main(int argc, char** argv)
114 {
115  testing::InitGoogleTest(&argc, argv);
116  return RUN_ALL_TESTS();
117 }
MoveIt-based cost functions that can be passed to STOMP via a ComposableTask.
CostFn getCostFunctionFromStateValidator(const StateValidatorFn &state_validator_fn, double interpolation_step_size)
CostFn sum(const std::vector< CostFn > &cost_functions)
constexpr double PENALTY
int main(int argc, char **argv)
constexpr size_t VARIABLES
constexpr size_t TIMESTEPS
TEST(NoiseGeneratorTest, testGetCostFunctionAllValidStates)