TSPSolver.cpp
Go to the documentation of this file.
1/**
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @author Fabian Reister ( fabian dot kit at edu )
17 * @date 2025
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22#include "TSPSolver.h"
23
24#include <cmath>
25#include <cstddef>
26#include <cstdint>
27#include <optional>
28#include <vector>
29
30#include <Eigen/Core>
31
32#include <range/v3/algorithm/reverse.hpp>
33#include <range/v3/range/conversion.hpp>
34#include <range/v3/view/concat.hpp>
35#include <range/v3/view/transform.hpp>
36
40
42{
43
44 namespace detail
45 {
46 // Compute full distance matrix from points
47 std::vector<std::vector<double>>
48 buildDistanceMatrix(const std::vector<Eigen::Vector2f>& pts)
49 {
50 const size_t n = pts.size();
51 std::vector<std::vector<double>> d(n, std::vector<double>(n, 0.0));
52 for (size_t i = 0; i < n; ++i)
53 {
54 for (size_t j = 0; j < n; ++j)
55 {
56 if (i == j)
57 {
58 continue;
59 }
60 d[i][j] = (pts.at(i) - pts.at(j)).norm();
61 }
62 }
63 return d;
64 }
65
66 // Held-Karp (exact) TSP solver returning (cost, tour)
67 // - start at node 0, return to node 0
68 // - returns INF if impossible
69 std::pair<double, std::vector<int>>
70 heldKarpTsp(const std::vector<std::vector<double>>& d)
71 {
72 const int n = static_cast<int>(d.size());
73 if (n == 0)
74 {
75 return {0.0, {}};
76 }
77 if (n == 1)
78 {
79 return {0.0, {0}};
80 }
81
82 const int FULL = 1 << n;
83 const double INF = 1e100;
84
85 // dp[mask][i] = minimal cost to reach subset `mask` ending at `i` (mask includes i)
86 // only store masks where bit 0 is set (we always start from 0)
87 std::vector<std::vector<double>> dp(FULL, std::vector<double>(n, INF));
88 std::vector<std::vector<int>> parent(FULL, std::vector<int>(n, -1));
89
90 dp[1 << 0][0] = 0.0; // start at node 0
91
92 for (int mask = 0; mask < FULL; ++mask)
93 {
94 if (!(mask & 1))
95 {
96 continue; // ensure start node is included
97 }
98 for (int u = 0; u < n; ++u)
99 {
100 if (!(mask & (1 << u)))
101 {
102 continue;
103 }
104 double cur = dp[mask][u];
105 if (cur >= INF)
106 {
107 continue;
108 }
109 for (int v = 0; v < n; ++v)
110 {
111 if (mask & (1 << v))
112 {
113 continue; // v must be outside mask
114 }
115 int nxt = mask | (1 << v);
116 double nd = cur + d[u][v];
117 if (nd < dp[nxt][v])
118 {
119 dp[nxt][v] = nd;
120 parent[nxt][v] = u;
121 }
122 }
123 }
124 }
125
126 // close the tour back to 0
127 double best = INF;
128 int best_last = -1;
129 int final_mask = FULL - 1;
130 for (int last = 1; last < n; ++last)
131 {
132 double cost = dp[final_mask][last] + d[last][0];
133 if (cost < best)
134 {
135 best = cost;
136 best_last = last;
137 }
138 }
139
140 if (best_last == -1)
141 {
142 return {INF, {}};
143 }
144
145 // reconstruct path
146 std::vector<int> tour;
147 int mask = final_mask;
148 int cur = best_last;
149 while (cur != -1)
150 {
151 tour.push_back(cur);
152 int p = parent[mask][cur];
153 mask ^= (1 << cur);
154 cur = p;
155 }
156
157 ranges::reverse(tour);
158
159 // tour is 0 -> ... -> best_last -> 0, but we appended 0 at beginning: ensure return to 0
160 tour.push_back(0);
161
162 ARMARX_INFO << VAROUT(tour);
163
164 // sanity check
165 ARMARX_CHECK_EQUAL(tour.size(), static_cast<std::size_t>(n + 1));
166
167 return {best, tour};
168 }
169 } // namespace detail
170
171 TSPSolver::TSPSolver(const Parameters& params) : params_(params)
172 {
173 // Initialize with params if needed
174 }
175
176 std::optional<TSPSolver::Result>
177 TSPSolver::solve(const std::vector<Eigen::Vector2f>& pts,
178 const Eigen::Vector2f& startingPosition) const
179 {
180 const std::vector<Eigen::Vector2f> startPt{startingPosition};
181
182 // we internally add the starting position as the first point
183 std::vector<Eigen::Vector2f> points =
184 ranges::views::concat(startPt, pts) | ranges::to<std::vector<Eigen::Vector2f>>();
185
186 auto d = detail::buildDistanceMatrix(points);
187 auto [cost, tour] = detail::heldKarpTsp(d);
188
189 if (cost >= 1e90)
190 {
191 ARMARX_ERROR << "TSP solver failed to find a solution.";
192 return std::nullopt;
193 }
194
195 ARMARX_INFO << "TSP tour computed: " << tour;
196 ARMARX_INFO << "TSP tour cost (millimeters): " << cost;
197
198 // the first and last point are the starting position
199 // we remove both from the orderIndices
200
201 auto orderedPoints =
202 ranges::views::transform(tour,
203 [&points](std::int64_t idx) noexcept
204 { return points.at(static_cast<std::size_t>(idx)); }) |
205 ranges::to<std::vector<Eigen::Vector2f>>();
206
207 // remove starting position from order indices
208 orderedPoints.erase(orderedPoints.begin());
209 // remove return to starting position
210 orderedPoints.erase(orderedPoints.end() - 1);
211
212 auto orderIndices = tour |
213 ranges::views::transform([](std::int64_t idx) noexcept
214 { return static_cast<std::size_t>(idx); }) |
215 ranges::to<std::vector<std::size_t>>();
216
217 // remove starting position
218 orderIndices.erase(orderIndices.begin());
219 // remove return to starting position
220 orderIndices.erase(orderIndices.end() - 1);
221
222 // decrement indices by 1 to account for removed starting position
223 for (auto& idx : orderIndices)
224 {
225 --idx;
226 }
227
228 // remove starting position from tour
229 return Result{.orderedPoints = orderedPoints, .orderIndices = orderIndices};
230 }
231
232
233} // namespace armarx::navigation::algorithms::tsp
#define VAROUT(x)
std::optional< Result > solve(const std::vector< Eigen::Vector2f > &points, const Eigen::Vector2f &startingPosition) const
#define ARMARX_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
std::vector< std::vector< double > > buildDistanceMatrix(const std::vector< Eigen::Vector2f > &pts)
Definition TSPSolver.cpp:48
std::pair< double, std::vector< int > > heldKarpTsp(const std::vector< std::vector< double > > &d)
Definition TSPSolver.cpp:70
This file is part of ArmarX.
Definition TSPSolver.cpp:42