Add startPoint and endPoint support to TSP solver and Python wrapper; add tests

- Enhanced the C++ TSP solver to accept optional start and end points, so the route can begin and/or end at the closest point to specified coordinates.
- Updated the Python pybind11 wrapper and PathUtils.sort_locations_tsp to support startPoint and endPoint as named parameters.
- Added a new Python test suite (TestTSPSolver.py) to verify correct handling of start/end points and integration with PathUtils.
- Registered the new test in TestCAMApp.py and CMakeLists.txt for automatic test discovery.
This commit is contained in:
Billy Huddleston
2025-10-17 15:03:10 -04:00
parent f9347c781b
commit 428948699a
7 changed files with 373 additions and 9 deletions

View File

@@ -25,13 +25,72 @@
namespace py = pybind11;
std::vector<int> tspSolvePy(const std::vector<std::pair<double, double>>& points)
std::vector<int> tspSolvePy(const std::vector<std::pair<double, double>>& points,
const py::object& startPoint = py::none(),
const py::object& endPoint = py::none())
{
std::vector<TSPPoint> pts;
for (const auto& p : points) {
pts.emplace_back(p.first, p.second);
}
return TSPSolver::solve(pts);
// Handle optional start point
TSPPoint* pStartPoint = nullptr;
TSPPoint startPointObj(0, 0);
if (!startPoint.is_none()) {
try {
// Use py::cast to convert to standard C++ types
auto sp = startPoint.cast<std::vector<double>>();
if (sp.size() >= 2) {
startPointObj.x = sp[0];
startPointObj.y = sp[1];
pStartPoint = &startPointObj;
}
}
catch (py::cast_error&) {
// If casting fails, try accessing elements directly
try {
if (py::len(startPoint) >= 2) {
startPointObj.x = py::cast<double>(startPoint.attr("__getitem__")(0));
startPointObj.y = py::cast<double>(startPoint.attr("__getitem__")(1));
pStartPoint = &startPointObj;
}
}
catch (py::error_already_set&) {
// Ignore if we can't access the elements
}
}
}
// Handle optional end point
TSPPoint* pEndPoint = nullptr;
TSPPoint endPointObj(0, 0);
if (!endPoint.is_none()) {
try {
// Use py::cast to convert to standard C++ types
auto ep = endPoint.cast<std::vector<double>>();
if (ep.size() >= 2) {
endPointObj.x = ep[0];
endPointObj.y = ep[1];
pEndPoint = &endPointObj;
}
}
catch (py::cast_error&) {
// If casting fails, try accessing elements directly
try {
if (py::len(endPoint) >= 2) {
endPointObj.x = py::cast<double>(endPoint.attr("__getitem__")(0));
endPointObj.y = py::cast<double>(endPoint.attr("__getitem__")(1));
pEndPoint = &endPointObj;
}
}
catch (py::error_already_set&) {
// Ignore if we can't access the elements
}
}
}
return TSPSolver::solve(pts, pStartPoint, pEndPoint);
}
PYBIND11_MODULE(tsp_solver, m)
@@ -40,5 +99,12 @@ PYBIND11_MODULE(tsp_solver, m)
m.def("solve",
&tspSolvePy,
py::arg("points"),
"Solve TSP for a list of (x, y) points using 2-Opt, returns visit order");
py::arg("startPoint") = py::none(),
py::arg("endPoint") = py::none(),
"Solve TSP for a list of (x, y) points using 2-Opt, returns visit order.\n"
"Optional arguments:\n"
"- startPoint: Optional [x, y] point where the path should start (closest point will be "
"chosen)\n"
"- endPoint: Optional [x, y] point where the path should end (closest point will be "
"chosen)");
}