Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ EOF
A VRPLIB instance contains problem **specifications** and problem **data**.
- Specifications are key-value pairs separated by a colon. In the example above, `NAME` and `EDGE_WEIGHT_TYPE` are the two data specifications.
- Data are explicit array-like values such as customer coordinates or service times.
Each data section should start with a header name that ends with `_SECTION`, e.g., `NODE_COORD_SECTION` and `SERVICE_TIME_SECTION`. It is then followed by rows of values and each row must start with an index representing the depot or customer.
There are two exceptions: values in `EDGE_WEIGHT_SECTION` and `DEPOT_SECTION` should not start with an index.
Each data section should start with a header name that ends with `_SECTION`, e.g., `NODE_COORD_SECTION` and `SERVICE_TIME_SECTION`. Section names may also end with a colon, e.g., `DEMAND_SECTION:`. The name must be followed by rows of values and each row must start with an index representing the depot or customer.
There are two exceptions to this rule: values in `EDGE_WEIGHT_SECTION` and `DEPOT_SECTION` should not start with an index.

Besides the rules outlined above, `vrplib` is not strict about the naming of specifications or sections.
This means that you can use `vrplib` to read VRPLIB instances with custom specifications like `MY_SPECIFICATION: SOME_VALUE` and custom section names like `MY_SECTION`.
Expand Down
28 changes: 28 additions & 0 deletions tests/parse/test_parse_vrplib.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pathlib import Path
from textwrap import dedent

import numpy as np
from numpy.testing import assert_, assert_equal, assert_raises
Expand Down Expand Up @@ -275,3 +276,30 @@ def test_empty_text():
"""
actual = parse_vrplib("")
assert_equal(actual, {})


def test_section_with_colon():
"""
Tests if a section name containing a colon is parsed correctly.
See https://github.com/PyVRP/VRPLIB/issues/132 for details.
"""
instance = dedent(
"""
DIMENSION: 1
EDGE_WEIGHT_TYPE: EXACT_2D
NODE_COORD_SECTION:
1 1 1
DEMAND_SECTION :
1 10
DEPOT_SECTION
1
EOF
"""
)

result = parse_vrplib(instance) # should not raise
assert_equal(result["dimension"], 1)
assert_equal(result["edge_weight_type"], "EXACT_2D")
assert_equal(result["demand"], [10])
assert_equal(result["node_coord"], [[1, 1]])
assert_equal(result["depot"], [0])
13 changes: 7 additions & 6 deletions vrplib/parse/parse_vrplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,12 @@ def group_specifications_and_sections(lines: list[str]):
if idx < end_section: # Skip all lines of the current section
continue

if ":" in line:
specs.append(line)
elif "_SECTION" in line:
start = lines.index(line)
if "_SECTION" in line:
start = idx
end_section = start + 1

for next_line in lines[start + 1 :]:
if ":" in next_line:
if ":" in next_line and "_SECTION" not in next_line:
raise ValueError("Specification presented after section.")
Comment on lines +79 to 80
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud, but what about EOF:. Should we let that be OK?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking, the paper includes EOF:. But it's such a weird case that I doubt that anyone will have problems with this. If they do, they can make a new issue :-).


# The current section ends when a next section or an EOF token
Expand All @@ -89,6 +87,8 @@ def group_specifications_and_sections(lines: list[str]):
end_section += 1

sections.append(lines[start:end_section])
elif ":" in line:
specs.append(line)
else:
msg = "Instance does not conform to the VRPLIB format."
raise RuntimeError(msg)
Expand All @@ -111,7 +111,8 @@ def parse_section(
"""
Parses the data section lines.
"""
name = lines[0].strip().removesuffix("_SECTION").lower()
# Some section names include colons, so we strip those as well.
name = lines[0].strip(" :").removesuffix("_SECTION").lower()
rows = [[infer_type(n) for n in line.split()] for line in lines[1:]]

if name == "edge_weight":
Expand Down