Skip to content

Commit 4e7d7b3

Browse files
authored
chore: support TSPLIB95 datasets in the TSP example (#415)
1 parent d5a2993 commit 4e7d7b3

File tree

10 files changed

+385
-85
lines changed

10 files changed

+385
-85
lines changed

examples/src/main/java/ai/timefold/solver/examples/common/persistence/AbstractTxtSolutionImporter.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,11 @@ public String[] splitBy(String line, String delimiterRegex, String delimiterName
356356
}
357357
lineTokenList.add(token);
358358
}
359-
lineTokens = lineTokenList.toArray(new String[0]);
359+
// Empty token == somewhere in there was an extra empty space
360+
lineTokens = lineTokenList
361+
.stream()
362+
.filter(token -> !token.isEmpty())
363+
.toArray(String[]::new);
360364
}
361365
if (minimumNumberOfTokens != null && lineTokens.length < minimumNumberOfTokens) {
362366
throw new IllegalArgumentException("Read line (" + line + ") has " + lineTokens.length

examples/src/main/java/ai/timefold/solver/examples/tsp/domain/location/AirLocation.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ public AirLocation(long id, double latitude, double longitude) {
1616
@Override
1717
public long getDistanceTo(Location location) {
1818
double distance = getAirDistanceDoubleTo(location);
19-
// Multiplied by 1000 to avoid floating point arithmetic rounding errors
20-
return (long) (distance * 1000.0 + 0.5);
19+
return adjust(distance);
2120
}
2221

2322
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
package ai.timefold.solver.examples.tsp.domain.location;
22

33
public enum DistanceType {
4-
/**
5-
* Requires that all {@link Location} instances are of type {@link AirLocation}.
6-
*/
7-
AIR_DISTANCE,
8-
/**
9-
* Requires that all {@link Location} instances are of type {@link RoadLocation}.
10-
*/
11-
ROAD_DISTANCE;
4+
AIR_DISTANCE(AirLocation::new),
5+
ROAD_DISTANCE(RoadLocation::new),
6+
GEOGRAPHIC_DISTANCE(GeoLocation::new),
7+
PSEUDO_ROAD_DISTANCE(PseudoRoadLocation::new); // Niche distance type for TSPLIB95.
8+
9+
private final LocationFunction locationFunction;
10+
11+
DistanceType(LocationFunction locationFunction) {
12+
this.locationFunction = locationFunction;
13+
}
14+
15+
public <Location_ extends Location> Location_ createLocation(long id, double x, double y) {
16+
return (Location_) locationFunction.apply(id, x, y);
17+
}
18+
19+
interface LocationFunction {
20+
Location apply(long id, double x, double y);
21+
}
22+
1223
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package ai.timefold.solver.examples.tsp.domain.location;
2+
3+
/**
4+
* Used with {@link DistanceType#GEOGRAPHIC_DISTANCE}.
5+
*/
6+
public class GeoLocation extends Location {
7+
8+
private static final double PI = 3.141592;
9+
private static final double RRR = 6378.388;
10+
11+
public GeoLocation() {
12+
}
13+
14+
public GeoLocation(long id, double x, double y) {
15+
super(id, toCoordinate(x), toCoordinate(y));
16+
}
17+
18+
@Override
19+
public long getDistanceTo(Location location) {
20+
double q1 = Math.cos(location.longitude - longitude);
21+
double q2 = Math.cos(location.latitude - latitude);
22+
double q3 = Math.cos(location.latitude + latitude);
23+
return adjust(RRR * Math.acos(0.5 * ((1.0 + q1) * q2 - (1.0 - q1) * q3)) + 1.0);
24+
}
25+
26+
private static double toCoordinate(double i) {
27+
long deg = adjust(i);
28+
double min = i - deg;
29+
return PI * (deg + 5.0 * min / 3.0) / 180.0;
30+
}
31+
32+
}

examples/src/main/java/ai/timefold/solver/examples/tsp/domain/location/Location.java

+4
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ public double getAngle(Location location) {
9898
return Math.atan2(latitudeDifference, longitudeDifference);
9999
}
100100

101+
protected static long adjust(double distance) {
102+
return (long) (distance + 0.5); // +0.5 to avoid floating point rounding errors
103+
}
104+
101105
@Override
102106
public String toString() {
103107
if (name == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package ai.timefold.solver.examples.tsp.domain.location;
2+
3+
public class PseudoRoadLocation extends Location {
4+
5+
public PseudoRoadLocation() {
6+
}
7+
8+
public PseudoRoadLocation(long id, double x, double y) {
9+
super(id, x, y);
10+
}
11+
12+
@Override
13+
public long getDistanceTo(Location location) {
14+
double xd = location.latitude - latitude;
15+
double yd = location.longitude - longitude;
16+
double rij = Math.sqrt((xd * xd + yd * yd) / 10.0);
17+
long tij = adjust(rij);
18+
return tij < rij ? tij + 1 : tij;
19+
}
20+
21+
}

examples/src/main/java/ai/timefold/solver/examples/tsp/domain/location/RoadLocation.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@ public long getDistanceTo(Location location) {
4343
if (this == location) {
4444
return 0L;
4545
}
46-
double distance = travelDistanceMap.get((RoadLocation) location);
47-
// Multiplied by 1000 to avoid floating point arithmetic rounding errors
48-
return (long) (distance * 1000.0 + 0.5);
46+
Double distance = travelDistanceMap.get((RoadLocation) location);
47+
if (distance == null) {
48+
throw new IllegalStateException("The travelDistanceMap in location (%s) does not contain the key (%s)."
49+
.formatted(this, location));
50+
}
51+
return adjust(distance);
4952
}
5053

5154
}

examples/src/main/java/ai/timefold/solver/examples/tsp/persistence/TspImageStipplerImporter.java

+1-17
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
import ai.timefold.solver.examples.common.persistence.AbstractPngSolutionImporter;
1010
import ai.timefold.solver.examples.common.persistence.SolutionConverter;
1111
import ai.timefold.solver.examples.tsp.app.TspApp;
12-
import ai.timefold.solver.examples.tsp.domain.Domicile;
1312
import ai.timefold.solver.examples.tsp.domain.TspSolution;
14-
import ai.timefold.solver.examples.tsp.domain.Visit;
1513
import ai.timefold.solver.examples.tsp.domain.location.AirLocation;
1614
import ai.timefold.solver.examples.tsp.domain.location.DistanceType;
1715
import ai.timefold.solver.examples.tsp.domain.location.Location;
@@ -98,21 +96,7 @@ private void floydSteinbergDithering() {
9896
}
9997

10098
private void createVisitList() {
101-
List<Location> locationList = tspSolution.getLocationList();
102-
List<Visit> visitList = new ArrayList<>(locationList.size() - 1);
103-
int count = 0;
104-
for (Location location : locationList) {
105-
if (count < 1) {
106-
Domicile domicile = new Domicile(location.getId(), location);
107-
tspSolution.setDomicile(domicile);
108-
} else {
109-
Visit visit = new Visit(location.getId(), location);
110-
// Notice that we leave the PlanningVariable properties on null
111-
visitList.add(visit);
112-
}
113-
count++;
114-
}
115-
tspSolution.setVisitList(visitList);
99+
TspImporter.TspInputBuilder.createVisitsFromLocations(tspSolution);
116100
}
117101

118102
}

0 commit comments

Comments
 (0)