Skip to content

Datasets

skroute.datasets bundles two families of routing instances and a reader for the TSPLIB 95 file format. Nothing is downloaded: every file ships inside the wheel.

  • 27 Waterloo national TSP instances — the populated places of a country as planar coordinates with the tour length published by the University of Waterloo under the TSPLIB EUC_2D metric (a proven optimum for 25 of them). Load one with load_tsp("wi29") or its country wrapper load_sahara().
  • Five road-cost tables — small multi-trip instances (Alicante–Murcia, Barcelona, Madrid, Valencia and Qatar) with cost, time and distance matrices retrieved from the Google Distance Matrix API.

Loaders return a Bunch: a dict whose keys are also attributes. The matrices carry no labels — they are plain float64 arrays — so pass labels=b.labels (and depot=b.depot) to fit or to RoutingProblem, or ask for as_frame=True and get labelled DataFrames.

>>> from skroute.datasets import load_tsp, load_barcelona
>>> wi = load_tsp("wi29")
>>> wi.coords.shape, wi.depot, wi.optimal_tour_length
((29, 2), 1, 27603)
>>> C = wi.distance_matrix()  # built once, cached; never built by the loader
>>> C.shape
(29, 29)
>>> bcn = load_barcelona()
>>> bcn.cost.shape, bcn.depot, bcn.units
((19, 19), 10000007, {'cost': 'EUR', 'time': 'h', 'distance': 'm'})

The 27 Waterloo instances

All 27 use EDGE_WEIGHT_TYPE: EUC_2D, so the distance between two cities is nint(sqrt(dx² + dy²)) with nint(x) = floor(x + 0.5) — what TSPBunch.distance_matrix() computes. Tour lengths are those published by the University of Waterloo (status of 2022-07-31): proven optima for 25 instances, while bm33708 and ch71009 are still open, so their optimal_tour_length is the best-known tour — within 0.031 % and 0.024 % of the lower bound — and not a proven optimum. The four instances above 20 000 nodes cannot be solved whole in scikit-route 2.0 (every solver works on a dense matrix; the one of ch71009 would take 40 GB): distance_matrix() refuses them unless force=True, and load_tsp(name, n_nodes=5000) gives a subsample whose optimum is unknown.

Name Country Wrapper Cities Tour length
wi29 Western Sahara load_sahara 29 27 603
dj38 Djibouti load_djibouti 38 6 656
qa194 Qatar load_qatar 194 9 352
uy734 Uruguay load_uruguay 734 79 114
zi929 Zimbabwe load_zimbabwe 929 95 345
lu980 Luxembourg load_luxembourg 980 11 340
rw1621 Rwanda load_rwanda 1 621 26 051
mu1979 Oman load_oman 1 979 86 891
nu3496 Nicaragua load_nicaragua 3 496 96 132
ca4663 Canada load_canada 4 663 1 290 319
tz6117 Tanzania load_tanzania 6 117 394 718
eg7146 Egypt load_egypt 7 146 172 386
ym7663 Yemen load_yemen 7 663 238 314
pm8079 Panama load_panama 8 079 114 855
ei8246 Ireland load_ireland 8 246 206 171
ar9152 Argentina load_argentina 9 152 837 479
ja9847 Japan load_japan 9 847 491 924
gr9882 Greece load_greece 9 882 300 899
kz9976 Kazakhstan load_kazakhstan 9 976 1 061 881
fi10639 Finland load_finland 10 639 520 527
mo14185 Morocco load_morocco 14 185 427 377
ho14473 Honduras load_honduras 14 473 177 092
it16862 Italy load_italy 16 862 557 315
vm22775 Vietnam load_vietnam 22 775 569 288
sw24978 Sweden load_sweden 24 978 855 597
bm33708 Burma load_burma 33 708 959 289 †
ch71009 China load_china 71 009 4 566 506 †

† Best-known tour, not proven optimal (Waterloo's lower bounds are 959 011 and 4 565 452).

Source: W. Cook et al., National Traveling Salesman Problems, University of Waterloo, https://www.math.uwaterloo.ca/tsp/world/countries.html; status table at https://www.math.uwaterloo.ca/tsp/world/summary.html.

load_tsp

load_tsp(name: str, *, n_nodes: int | None = None, random_state: int | None = 2019, mode: str | None = None) -> TSPBunch

Load one of the 27 Waterloo national TSP instances bundled with scikit-route.

Parameters:

Name Type Description Default
name str

Instance name, e.g. "wi29"; see list_tsp.

required
n_nodes int

Keep only n_nodes of the cities, sampled without replacement with numpy.random.default_rng(random_state); the first city of the file is always kept and the file order is preserved. optimal_tour_length is then None.

None
random_state int or None

Seed of the subsampling (only used with n_nodes).

2019
mode ('small', 'medium', 'big')

Deprecated 1.0 spelling of n_nodes: "small" keeps max(10, round(0.005 n)) cities, "medium" round(0.2 n), "big" all of them. Emits DeprecationWarning; removed in 3.0.

"small"

Returns:

Type Description
TSPBunch

With name, coords (float64 (n, 2), x/y as in the file), labels (int64, the file's 1-based ids), depot (the first label, 1), edge_weight_type ("EUC_2D" for all 27), optimal_tour_length (the tour length published by Waterloo: the proven optimum, except for bm33708 and ch71009 where it is the best-known tour; None when subsampled), DESCR, and the cached method distance_matrix(*, force=False). The matrix is never built by the loader.

Notes

The four instances above 20 000 nodes (vm22775, sw24978, bm33708, ch71009) cannot be solved whole in 2.0 (dense matrices only); their DESCR says so and shows load_tsp(name, n_nodes=5000). bm33708 and ch71009 are still open problems: their optimal_tour_length is the best-known tour, within 0.031 % and 0.024 % of Waterloo's lower bound, and the DESCR says so too.

References

W. Cook et al., National Traveling Salesman Problems, University of Waterloo, https://www.math.uwaterloo.ca/tsp/world/countries.html; status table at https://www.math.uwaterloo.ca/tsp/world/summary.html

Examples:

>>> from skroute.datasets import load_tsp
>>> wi = load_tsp("wi29")
>>> wi.coords.shape, wi.labels[:3].tolist(), wi.depot, wi.optimal_tour_length
((29, 2), [1, 2, 3], 1, 27603)
>>> small = load_tsp("qa194", n_nodes=20, random_state=0)
>>> small.coords.shape, int(small.labels[0]), small.optimal_tour_length
((20, 2), 1, None)

list_tsp

list_tsp() -> list[str]

Names of the 27 bundled Waterloo instances, from the smallest (wi29) to the largest (ch71009).

Returns:

Type Description
list of str

Examples:

>>> from skroute.datasets import list_tsp
>>> names = list_tsp()
>>> len(names), names[:3]
(27, ['wi29', 'dj38', 'qa194'])

TSPBunch

TSPBunch(**kwargs: Any)

Bases: Bunch

A Bunch of a coordinate instance with a cached distance_matrix().

Behaves like a dict with attribute access (b.coords or b["coords"]); keys() lists only the data fields. The distance matrix is a method, so it is never built by the loader and is cached under the private attribute _distance_matrix (not a key).

Examples:

>>> from skroute.datasets import load_tsp
>>> b = load_tsp("wi29")
>>> sorted(b)
['DESCR', 'coords', 'depot', 'edge_weight_type', 'labels', 'name', 'optimal_tour_length']
>>> C = b.distance_matrix()
>>> C.shape, C is b.distance_matrix()  # (n, n) and cached
((29, 29), True)

distance_matrix

distance_matrix(*, force: bool = False) -> NDArray[float64]

The dense (n, n) matrix under the instance's TSPLIB metric, computed once and cached.

Parameters:

Name Type Description Default
force bool

Build the matrix even above 20 000 nodes (a float64 matrix of ch71009 needs about 40 GB). Without it such instances raise ValueError and point to load_tsp(name, n_nodes=...).

False

Returns:

Type Description
ndarray of shape (n, n), dtype float64

skroute.preprocessing.distance_matrix(coords, metric="tsplib_euc_2d") for EUC_2D instances (the other TSPLIB coordinate types map to their tsplib_* metric). The same array object is returned on every call: copy it before modifying it.

Raises:

Type Description
ValueError

Above 20 000 nodes without force, or when edge_weight_type is not one of EUC_2D, CEIL_2D, MAN_2D, ATT, GEO (never a silent fall-back to the planar metric).

Country wrappers

Each one is load_tsp("<name>", ...) under its 1.0 name and accepts the same n_nodes=, random_state= and (deprecated) mode= keywords. As in 1.0, mode may also be the first positional argument (load_qatar("small")); it emits the same DeprecationWarning.

load_sahara

load_sahara(mode: str | None = None, **kwargs: Any) -> TSPBunch

Western Sahara, wi29 (29 cities, optimum 27603); load_tsp("wi29", **kwargs).

load_djibouti

load_djibouti(mode: str | None = None, **kwargs: Any) -> TSPBunch

Djibouti, dj38 (38 cities, optimum 6656); load_tsp("dj38", **kwargs).

load_qatar

load_qatar(mode: str | None = None, **kwargs: Any) -> TSPBunch

Qatar, qa194 (194 cities, optimum 9352); load_tsp("qa194", **kwargs).

load_uruguay

load_uruguay(mode: str | None = None, **kwargs: Any) -> TSPBunch

Uruguay, uy734 (734 cities, optimum 79114); load_tsp("uy734", **kwargs).

load_zimbabwe

load_zimbabwe(mode: str | None = None, **kwargs: Any) -> TSPBunch

Zimbabwe, zi929 (929 cities, optimum 95345); load_tsp("zi929", **kwargs).

load_luxembourg

load_luxembourg(mode: str | None = None, **kwargs: Any) -> TSPBunch

Luxembourg, lu980 (980 cities, optimum 11340); load_tsp("lu980", **kwargs).

load_rwanda

load_rwanda(mode: str | None = None, **kwargs: Any) -> TSPBunch

Rwanda, rw1621 (1621 cities, optimum 26051); load_tsp("rw1621", **kwargs).

load_oman

load_oman(mode: str | None = None, **kwargs: Any) -> TSPBunch

Oman, mu1979 (1979 cities, optimum 86891); load_tsp("mu1979", **kwargs).

load_nicaragua

load_nicaragua(mode: str | None = None, **kwargs: Any) -> TSPBunch

Nicaragua, nu3496 (3496 cities, optimum 96132); load_tsp("nu3496", **kwargs).

load_canada

load_canada(mode: str | None = None, **kwargs: Any) -> TSPBunch

Canada, ca4663 (4663 cities, optimum 1290319); load_tsp("ca4663", **kwargs).

load_tanzania

load_tanzania(mode: str | None = None, **kwargs: Any) -> TSPBunch

Tanzania, tz6117 (6117 cities, optimum 394718); load_tsp("tz6117", **kwargs).

load_egypt

load_egypt(mode: str | None = None, **kwargs: Any) -> TSPBunch

Egypt, eg7146 (7146 cities, optimum 172386); load_tsp("eg7146", **kwargs).

load_yemen

load_yemen(mode: str | None = None, **kwargs: Any) -> TSPBunch

Yemen, ym7663 (7663 cities, optimum 238314); load_tsp("ym7663", **kwargs).

load_panama

load_panama(mode: str | None = None, **kwargs: Any) -> TSPBunch

Panama, pm8079 (8079 cities, optimum 114855); load_tsp("pm8079", **kwargs).

load_ireland

load_ireland(mode: str | None = None, **kwargs: Any) -> TSPBunch

Ireland, ei8246 (8246 cities, optimum 206171); load_tsp("ei8246", **kwargs).

load_argentina

load_argentina(mode: str | None = None, **kwargs: Any) -> TSPBunch

Argentina, ar9152 (9152 cities, optimum 837479); load_tsp("ar9152", **kwargs).

load_japan

load_japan(mode: str | None = None, **kwargs: Any) -> TSPBunch

Japan, ja9847 (9847 cities, optimum 491924); load_tsp("ja9847", **kwargs).

load_greece

load_greece(mode: str | None = None, **kwargs: Any) -> TSPBunch

Greece, gr9882 (9882 cities, optimum 300899); load_tsp("gr9882", **kwargs).

load_kazakhstan

load_kazakhstan(mode: str | None = None, **kwargs: Any) -> TSPBunch

Kazakhstan, kz9976 (9976 cities, optimum 1061881); load_tsp("kz9976", **kwargs).

load_finland

load_finland(mode: str | None = None, **kwargs: Any) -> TSPBunch

Finland, fi10639 (10639 cities, optimum 520527); load_tsp("fi10639", **kwargs).

load_morocco

load_morocco(mode: str | None = None, **kwargs: Any) -> TSPBunch

Morocco, mo14185 (14185 cities, optimum 427377); load_tsp("mo14185", **kwargs).

load_honduras

load_honduras(mode: str | None = None, **kwargs: Any) -> TSPBunch

Honduras, ho14473 (14473 cities, optimum 177092); load_tsp("ho14473", **kwargs).

load_italy

load_italy(mode: str | None = None, **kwargs: Any) -> TSPBunch

Italy, it16862 (16862 cities, optimum 557315); load_tsp("it16862", **kwargs).

load_vietnam

load_vietnam(mode: str | None = None, **kwargs: Any) -> TSPBunch

Vietnam, vm22775 (22775 cities, optimum 569288); load_tsp("vm22775", **kwargs).

Above the 20 000-node dense-matrix ceiling of 2.0: subsample with n_nodes=.

load_sweden

load_sweden(mode: str | None = None, **kwargs: Any) -> TSPBunch

Sweden, sw24978 (24978 cities, optimum 855597); load_tsp("sw24978", **kwargs).

Above the 20 000-node dense-matrix ceiling of 2.0: subsample with n_nodes=.

load_burma

load_burma(mode: str | None = None, **kwargs: Any) -> TSPBunch

Burma, bm33708 (33708 cities, best-known tour 959289); load_tsp("bm33708", **kwargs).

Above the 20 000-node dense-matrix ceiling of 2.0: subsample with n_nodes=. Still an open problem: optimal_tour_length is the best-known tour (within 0.031 % of the lower bound), not a proven optimum.

load_china

load_china(mode: str | None = None, **kwargs: Any) -> TSPBunch

China, ch71009 (71009 cities, best-known tour 4566506); load_tsp("ch71009", **kwargs).

Above the 20 000-node dense-matrix ceiling of 2.0: subsample with n_nodes=. Still an open problem: optimal_tour_length is the best-known tour (within 0.024 % of the lower bound), not a proven optimum.

Road-cost tables

Five long tables of (origin, destination, cost, time, distance) rows pivoted into symmetric matrices with pairs_to_matrix. cost is in EUR (the Spanish files) or kilometres (Qatar, which has no money column), time in hours, distance in metres; coords are (latitude, longitude) in decimal degrees and depot is the first id of each file. The Spanish time matrices come from the hours column of the tables, which adds a fixed 7-minute stop to every leg (hours = (secs + 420) / 3600). The Qatar table records the pair (104, 111) as 0 m / 0 s although the two places are about 4 km apart; the matrices reproduce that zero (see the DESCR).

Loader Places Depot cost unit Rows in the table
load_alicante_murcia 8 10000002 EUR 36
load_barcelona 19 10000007 EUR 190
load_madrid 18 10000016 EUR 171
load_valencia 14 10000022 EUR 105
load_qatar_costs 192 1 km 18 336

load_alicante_murcia

load_alicante_murcia(*, as_frame: bool = False) -> Bunch

Road costs between 8 places of Alicante and Murcia (Spain); depot 10000002.

Parameters:

Name Type Description Default
as_frame bool

Return cost, time and distance as labelled pandas.DataFrame objects and the long table under frame. Requires pandas (pip install scikit-route[pandas]).

False

Returns:

Type Description
Bunch

cost (EUR), time (hours), distance (metres) as symmetric float64 (8, 8) matrices with a zero diagonal; coords as (latitude, longitude); labels (int64 ids in first-appearance order); depot (10000002, the first id); units; DESCR; frame (None unless as_frame=True).

Notes

The matrices carry no labels: pass labels=b.labels, depot=b.depot to fit. Parsed with the stdlib csv module (the addresses contain commas).

Examples:

>>> from skroute.datasets import load_alicante_murcia
>>> b = load_alicante_murcia()
>>> b.cost.shape, b.depot, b.units
((8, 8), 10000002, {'cost': 'EUR', 'time': 'h', 'distance': 'm'})
>>> bool((b.cost == b.cost.T).all()) and float(b.cost.diagonal().max()) == 0.0
True

load_barcelona

load_barcelona(*, as_frame: bool = False) -> Bunch

Road costs between 19 places of Barcelona (Spain); depot 10000007.

Parameters:

Name Type Description Default
as_frame bool

Return cost, time and distance as labelled pandas.DataFrame objects and the long table under frame. Requires pandas.

False

Returns:

Type Description
Bunch

cost (EUR), time (hours), distance (metres) as symmetric float64 (19, 19) matrices; coords (latitude, longitude); labels; depot == 10000007; units; DESCR; frame.

Examples:

>>> from skroute.datasets import load_barcelona
>>> bcn = load_barcelona()
>>> bcn.time.shape, bcn.depot, bcn.units["time"]
((19, 19), 10000007, 'h')
>>> int(bcn.labels[0]) == bcn.depot
True

load_madrid

load_madrid(*, as_frame: bool = False) -> Bunch

Road costs between 18 places of Madrid (Spain); depot 10000016.

Parameters:

Name Type Description Default
as_frame bool

Return labelled pandas.DataFrame matrices and the long table. Requires pandas.

False

Returns:

Type Description
Bunch

cost (EUR), time (hours), distance (metres) as float64 (18, 18) matrices; coords; labels; depot == 10000016; units; DESCR; frame.

Examples:

>>> from skroute.datasets import load_madrid
>>> b = load_madrid()
>>> b.cost.shape, b.depot
((18, 18), 10000016)

load_valencia

load_valencia(*, as_frame: bool = False) -> Bunch

Road costs between 14 places of Valencia (Spain); depot 10000022.

Parameters:

Name Type Description Default
as_frame bool

Return labelled pandas.DataFrame matrices and the long table. Requires pandas.

False

Returns:

Type Description
Bunch

cost (EUR), time (hours), distance (metres) as float64 (14, 14) matrices; coords; labels; depot == 10000022; units; DESCR; frame.

Examples:

>>> from skroute.datasets import load_valencia
>>> b = load_valencia()
>>> b.cost.shape, b.depot
((14, 14), 10000022)

load_qatar_costs

load_qatar_costs(*, as_frame: bool = False) -> Bunch

Road distances and driving times between 192 places of Qatar; depot 1.

Parameters:

Name Type Description Default
as_frame bool

Return labelled pandas.DataFrame matrices and the long table. Requires pandas.

False

Returns:

Type Description
Bunch

cost in kilometres (the table has no money column), time in hours (seconds / 3600), distance in metres, as symmetric float64 (192, 192) matrices; coords (latitude, longitude); labels (ids 1..193 with one absent, first-appearance order); depot == 1; units == {"cost": "km", "time": "h", "distance": "m"}; DESCR; frame.

Notes

In 1.0 load_costs_qatar() returned the Valencia table; that name survives as a deprecated alias of this function.

The source table records the pair (104, 111) as 0 m / 0 s although the two places are about 4 km apart, so cost, time and distance each hold one zero off the diagonal (the Spanish tables have none). It is kept as in the source; fill it (e.g. with the haversine distance) if your use needs positive legs.

Examples:

>>> from skroute.datasets import load_qatar_costs
>>> q = load_qatar_costs()
>>> q.cost.shape, q.depot, q.units["cost"]
((192, 192), 1, 'km')
>>> bool((q.cost == q.distance / 1000.0).all())  # cost is the road distance in km
True

load_costs_qatar

load_costs_qatar(*, as_frame: bool = False) -> Bunch

Deprecated alias of load_qatar_costs (1.0 loaded Valencia under this name).

Emits DeprecationWarning; removed in 3.0.

Examples:

>>> from skroute.datasets import load_costs_qatar
>>> load_costs_qatar().cost.shape
(192, 192)

TSPLIB reader

Pure-Python readers for the TSPLIB 95 formats. read_tsplib handles the coordinate types EUC_2D, CEIL_2D, MAN_2D, ATT and GEO (coordinates are returned raw; convert them with distance_matrix and the matching tsplib_* metric) and EXPLICIT matrices in the formats FULL_MATRIX, UPPER_ROW, LOWER_ROW, UPPER_DIAG_ROW and LOWER_DIAG_ROW; other edge-weight types raise ValueError("... is not supported in this version"). Both readers tolerate KEY : value and KEY: value, CRLF line endings, a UTF-8 BOM, UTF-8 or latin-1 text, data written on the section keyword's own line and a missing EOF.

read_tsplib

read_tsplib(path_or_file: str | PathLike[str] | IO[str] | IO[bytes]) -> Bunch

Read a TSPLIB 95 .tsp file (coordinates or an explicit matrix).

Parameters:

Name Type Description Default
path_or_file path-like or file object

Path of the file, or an open text/binary file object.

required

Returns:

Type Description
Bunch

With the fields

  • name, comment (COMMENT lines joined with newlines; None when absent), type ("TSP", "ATSP", ...: the first word of the TYPE entry, so a trailing author note is dropped), dimension (int), edge_weight_type, edge_weight_format (None unless EXPLICIT);
  • coords: float64 (n, 2) for the coordinate types EUC_2D, CEIL_2D, MAN_2D, ATT and GEO (kept raw -- GEO stays in DDD.MM notation; convert with skroute.preprocessing.distance_matrix and the matching tsplib_* metric), otherwise None;
  • cost: float64 (n, n) for EXPLICIT in the formats FULL_MATRIX, UPPER_ROW, LOWER_ROW, UPPER_DIAG_ROW and LOWER_DIAG_ROW (triangular formats are mirrored), otherwise None;
  • display_coords: float64 (n, 2) from a DISPLAY_DATA_SECTION, else None;
  • labels: int64 (n,) node ids exactly as written in the file (1-based in every TSPLIB instance), in file order; they must be unique.

Raises:

Type Description
ValueError

For an unsupported EDGE_WEIGHT_TYPE ("EDGE_WEIGHT_TYPE EUC_3D is not supported in this version"), a missing or unsupported EDGE_WEIGHT_FORMAT with EXPLICIT, a DIMENSION below 1 or disagreeing with a section's size, a repeated node id, or a malformed line.

Notes

Tolerant of KEY : value and KEY: value (dj38.tsp mixes both), CRLF line endings, indented data lines, data tokens on the section keyword's own line, a UTF-8 BOM, UTF-8 or latin-1 text, and a missing EOF. Sections this reader does not use (DEMAND_SECTION, FIXED_EDGES_SECTION, ...) are skipped; check type when reading anything other than a plain TSP. Pure Python: no pandas, no regular expressions.

References

G. Reinelt, TSPLIB 95, Universitaet Heidelberg, 1995.

Examples:

>>> import io
>>> from skroute.datasets import read_tsplib
>>> text = "NAME: tiny\nTYPE: TSP\nDIMENSION: 3\nEDGE_WEIGHT_TYPE: EUC_2D\n" \
...        "NODE_COORD_SECTION\n1 0 0\n2 3 4\n3 6 8\nEOF\n"
>>> b = read_tsplib(io.StringIO(text))
>>> b.name, b.dimension, b.edge_weight_type, b.coords.shape, b.labels.tolist()
('tiny', 3, 'EUC_2D', (3, 2), [1, 2, 3])
>>> text = "NAME: m3\nTYPE: TSP\nDIMENSION: 3\nEDGE_WEIGHT_TYPE: EXPLICIT\n" \
...        "EDGE_WEIGHT_FORMAT: UPPER_ROW\nEDGE_WEIGHT_SECTION\n5 9\n4\nEOF\n"
>>> read_tsplib(io.StringIO(text)).cost.tolist()
[[0.0, 5.0, 9.0], [5.0, 0.0, 4.0], [9.0, 4.0, 0.0]]

read_tsplib_tour

read_tsplib_tour(path_or_file: str | PathLike[str] | IO[str] | IO[bytes]) -> NDArray[int64]

Read a TSPLIB 95 .tour file and return the node ids of its first tour.

Parameters:

Name Type Description Default
path_or_file path-like or file object

Path of the file, or an open text/binary file object.

required

Returns:

Type Description
ndarray of int64, shape (n,)

The ids of the TOUR_SECTION in order (1-based, exactly as written), up to the -1 terminator; the closing return to the first node is implicit. Ids written on the TOUR_SECTION line itself count.

Raises:

Type Description
ValueError

If the file has no TOUR_SECTION, the tour is empty, or its length disagrees with a DIMENSION entry.

Examples:

>>> import io
>>> from skroute.datasets import read_tsplib_tour
>>> text = "NAME: tiny.tour\nTYPE: TOUR\nDIMENSION: 3\nTOUR_SECTION\n1\n3\n2\n-1\nEOF\n"
>>> read_tsplib_tour(io.StringIO(text)).tolist()
[1, 3, 2]