diff --git a/wpimath/src/main/java/edu/wpi/first/math/path/TravelingSalesman.java b/wpimath/src/main/java/edu/wpi/first/math/path/TravelingSalesman.java index 1b885fb7cf..83dd93ef0b 100644 --- a/wpimath/src/main/java/edu/wpi/first/math/path/TravelingSalesman.java +++ b/wpimath/src/main/java/edu/wpi/first/math/path/TravelingSalesman.java @@ -8,6 +8,8 @@ import edu.wpi.first.math.Num; import edu.wpi.first.math.Vector; import edu.wpi.first.math.geometry.Pose2d; import edu.wpi.first.math.optimization.SimulatedAnnealing; +import java.util.Arrays; +import java.util.Collections; import java.util.function.ToDoubleBiFunction; /** @@ -40,7 +42,8 @@ public class TravelingSalesman { } /** - * Finds the path through every pose that minimizes the cost. + * Finds the path through every pose that minimizes the cost. The first pose in the returned array + * is the first pose that was passed in. * * @param A Num defining the length of the path and the number of poses. * @param poses An array of Pose2ds the path must pass through. @@ -76,6 +79,9 @@ public class TravelingSalesman { solution[i] = poses[(int) indices.get(i, 0)]; } + // Rotate solution list until solution[0] = poses[0] + Collections.rotate(Arrays.asList(solution), -Arrays.asList(solution).indexOf(poses[0])); + return solution; } diff --git a/wpimath/src/main/native/include/frc/path/TravelingSalesman.h b/wpimath/src/main/native/include/frc/path/TravelingSalesman.h index 696e0d86da..d45f6203f8 100644 --- a/wpimath/src/main/native/include/frc/path/TravelingSalesman.h +++ b/wpimath/src/main/native/include/frc/path/TravelingSalesman.h @@ -44,7 +44,8 @@ class TravelingSalesman { : m_cost{std::move(cost)} {} /** - * Finds the path through every pose that minimizes the cost. + * Finds the path through every pose that minimizes the cost. The first pose + * in the returned array is the first pose that was passed in. * * This overload supports a statically-sized list of poses. * @@ -81,11 +82,17 @@ class TravelingSalesman { solution[i] = poses[static_cast(indices[i])]; } + // Rotate solution list until solution[0] = poses[0] + std::rotate(solution.begin(), + std::find(solution.begin(), solution.end(), poses[0]), + solution.end()); + return solution; } /** - * Finds the path through every pose that minimizes the cost. + * Finds the path through every pose that minimizes the cost. The first pose + * in the returned array is the first pose that was passed in. * * This overload supports a dynamically-sized list of poses for Python to use. * @@ -119,6 +126,11 @@ class TravelingSalesman { solution.emplace_back(poses[static_cast(indices[i])]); } + // Rotate solution list until solution[0] = poses[0] + std::rotate(solution.begin(), + std::find(solution.begin(), solution.end(), poses[0]), + solution.end()); + return solution; } diff --git a/wpimath/src/test/java/edu/wpi/first/math/path/TravelingSalesmanTest.java b/wpimath/src/test/java/edu/wpi/first/math/path/TravelingSalesmanTest.java index ef3f940954..cf4317721d 100644 --- a/wpimath/src/test/java/edu/wpi/first/math/path/TravelingSalesmanTest.java +++ b/wpimath/src/test/java/edu/wpi/first/math/path/TravelingSalesmanTest.java @@ -22,16 +22,10 @@ class TravelingSalesmanTest { private boolean isMatchingCycle(Pose2d[] expected, Pose2d[] actual) { assertEquals(expected.length, actual.length); - // Find first element in actual that matches expected - int actualStart = 0; - while (!actual[actualStart].equals(expected[0])) { - ++actualStart; - } - // Check actual has expected cycle (forward) var actualBufferForward = new CircularBuffer(actual.length); for (int i = 0; i < actual.length; ++i) { - actualBufferForward.addLast(actual[(actualStart + i) % actual.length]); + actualBufferForward.addLast(actual[i % actual.length]); } boolean matchesExpectedForward = true; for (int i = 0; i < expected.length; ++i) { @@ -41,7 +35,7 @@ class TravelingSalesmanTest { // Check actual has expected cycle (reverse) var actualBufferReverse = new CircularBuffer(actual.length); for (int i = 0; i < actual.length; ++i) { - actualBufferReverse.addFirst(actual[(actualStart + 1 + i) % actual.length]); + actualBufferReverse.addFirst(actual[(1 + i) % actual.length]); } boolean matchesExpectedReverse = true; diff --git a/wpimath/src/test/native/cpp/path/TravelingSalesmanTest.cpp b/wpimath/src/test/native/cpp/path/TravelingSalesmanTest.cpp index b8b622afdf..f46eb2040f 100644 --- a/wpimath/src/test/native/cpp/path/TravelingSalesmanTest.cpp +++ b/wpimath/src/test/native/cpp/path/TravelingSalesmanTest.cpp @@ -25,16 +25,10 @@ bool IsMatchingCycle(std::span expected, std::span actual) { assert(expected.size() == actual.size()); - // Find first element in actual that matches expected - size_t actualStart = 0; - while (actual[actualStart] != expected[0]) { - ++actualStart; - } - // Check actual has expected cycle (forward) wpi::circular_buffer actualBufferForward{expected.size()}; for (size_t i = 0; i < actual.size(); ++i) { - actualBufferForward.push_back(actual[(actualStart + i) % actual.size()]); + actualBufferForward.push_back(actual[i % actual.size()]); } bool matchesExpectedForward = true; for (size_t i = 0; i < expected.size(); ++i) { @@ -44,8 +38,7 @@ bool IsMatchingCycle(std::span expected, // Check actual has expected cycle (reverse) wpi::circular_buffer actualBufferReverse{expected.size()}; for (size_t i = 0; i < actual.size(); ++i) { - actualBufferReverse.push_front( - actual[(actualStart + 1 + i) % actual.size()]); + actualBufferReverse.push_front(actual[(1 + i) % actual.size()]); } bool matchesExpectedReverse = true; for (size_t i = 0; i < expected.size(); ++i) {