Skip to content

Commit

Permalink
Optional argument to compute edge weights (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
leonlan authored May 1, 2023
1 parent 1750995 commit d10da97
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 49 deletions.
37 changes: 24 additions & 13 deletions tests/parse/test_parse_solomon.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pathlib import Path

import numpy as np
from numpy.testing import assert_equal, assert_raises
from numpy.testing import assert_, assert_equal, assert_raises
from pytest import mark

from vrplib.parse.parse_solomon import parse_solomon
Expand Down Expand Up @@ -58,22 +58,23 @@ def test_does_not_raise(name):
parse_solomon(fh.read())


def test_parse_vrplib():
SOLOMON_INSTANCE = [
"C101",
"VEHICLE",
"NUMBER CAPACITY",
"25 200",
"CUSTOMER",
"CUST NO. XCOORD. YCOORD. DEMAND READY TIME DUE DATE SERVICE TIME",
"0 40 50 0 0 1236 0",
"1 45 68 10 912 967 90",
]


def test_parse_solomon():
"""
Checks if the Solomon instance lines are correctly parsed.
"""

SOLOMON_INSTANCE = [
"C101",
"VEHICLE",
"NUMBER CAPACITY",
"25 200",
"CUSTOMER",
"CUST NO. XCOORD. YCOORD. DEMAND READY TIME DUE DATE SERVICE TIME",
"0 40 50 0 0 1236 0",
"1 45 68 10 912 967 90",
]

actual = parse_solomon("\n".join(SOLOMON_INSTANCE))

dist = ((40 - 45) ** 2 + (50 - 68) ** 2) ** 0.5 # from 0 to 1
Expand All @@ -89,3 +90,13 @@ def test_parse_vrplib():
}

assert_equal(actual, desired)


def test_do_not_compute_edge_weights():
"""
Tests if the instance does not contain edge weights if the
`compute_edge_weights` is set to False.
"""
text = "\n".join(SOLOMON_INSTANCE)
actual = parse_solomon(text, compute_edge_weights=False)
assert_("edge_weight" not in actual)
35 changes: 34 additions & 1 deletion tests/parse/test_parse_vrplib.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pathlib import Path

import numpy as np
from numpy.testing import assert_equal, assert_raises
from numpy.testing import assert_, assert_equal, assert_raises
from pytest import mark

from vrplib.parse.parse_vrplib import (
Expand Down Expand Up @@ -200,3 +200,36 @@ def test_parse_vrplib_no_explicit_edge_weights():
}

assert_equal(actual, desired)


def test_parse_vrplib_do_not_compute_edge_weights():
"""
Tests if the edge weight section is not calculated when the instance does
not contain an explicit section and the user does not want to compute it.
"""
instance = "\n".join(
[
"NAME: VRPLIB",
"EDGE_WEIGHT_TYPE: FLOOR_2D",
"NODE_COORD_SECTION",
"1 0 1",
"2 1 0",
"SERVICE_TIME_SECTION",
"1 1",
"2 1",
"TIME_WINDOW_SECTION",
"1 1 2",
"2 1 2",
"EOF",
]
)
actual = parse_vrplib(instance, compute_edge_weights=False)
assert_("edge_weight" not in actual)


def test_empty_text():
"""
Tests if an empty text file is still read correctly.
"""
actual = parse_vrplib("")
assert_equal(actual, {})
72 changes: 49 additions & 23 deletions tests/read/test_read_instance.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import numpy as np
from numpy.testing import assert_equal, assert_raises
from numpy.testing import assert_, assert_equal, assert_raises
from pytest import mark

from vrplib.read import read_instance
Expand All @@ -17,43 +17,43 @@ def test_raise_unknown_instance_format(tmp_path, instance_format):
read_instance(path, instance_format)


VRPLIB_INSTANCE = [
"NAME: VRPLIB",
"EDGE_WEIGHT_TYPE: EUC_2D",
"NODE_COORD_SECTION",
"1 0 0",
"2 0 1",
"SERVICE_TIME_SECTION",
"1 1",
"TIME_WINDOW_SECTION",
"1 1 2",
"EOF",
]


def test_read_vrplib_instance(tmp_path):
"""
Tests if a VRPLIB instance is correctly read and parsed.
"""
name = "test.sol"
name = "vrplib.txt"

with open(tmp_path / name, "w") as fi:
instance = "\n".join(
[
"NAME: VRPLIB",
"EDGE_WEIGHT_TYPE: EXPLICIT",
"EDGE_WEIGHT_FORMAT: FULL_MATRIX",
"EDGE_WEIGHT_SECTION",
"0 1",
"1 0",
"SERVICE_TIME_SECTION",
"1 1",
"TIME_WINDOW_SECTION",
"1 1 2",
"EOF",
]
)
instance = "\n".join(VRPLIB_INSTANCE)
fi.write(instance)

desired = {
"name": "VRPLIB",
"edge_weight_type": "EXPLICIT",
"edge_weight_format": "FULL_MATRIX",
"edge_weight": np.array([[0, 1], [1, 0]]),
"edge_weight_type": "EUC_2D",
"node_coord": np.array([[0, 0], [0, 1]]),
"service_time": np.array([1]),
"time_window": np.array([[1, 2]]),
"edge_weight": np.array([[0, 1], [1, 0]]),
}

assert_equal(read_instance(tmp_path / name), desired)


_SOLOMON_INSTANCE = [
SOLOMON_INSTANCE = [
"C101",
"VEHICLE",
"NUMBER CAPACITY",
Expand All @@ -69,10 +69,10 @@ def test_read_solomon_instance(tmp_path):
"""
Tests if a Solomon instance is correctly read and parsed.
"""
name = "test.sol"
name = "solomon.txt"

with open(tmp_path / name, "w") as fi:
instance = "\n".join(_SOLOMON_INSTANCE)
instance = "\n".join(SOLOMON_INSTANCE)
fi.write(instance)

dist = ((40 - 45) ** 2 + (50 - 68) ** 2) ** 0.5 # from 0 to 1
Expand All @@ -89,3 +89,29 @@ def test_read_solomon_instance(tmp_path):

actual = read_instance(tmp_path / name, instance_format="solomon")
assert_equal(actual, desired)


def test_do_not_compute_edge_weights(tmp_path):
"""
Tests if the edge weights are not contained in the instance when the
corresponding argument is set to False.
"""
# Test VRPLIB instance
name = "vrplib.txt"

with open(tmp_path / name, "w") as fi:
instance = "\n".join(VRPLIB_INSTANCE)
fi.write(instance)

instance = read_instance(tmp_path / name, compute_edge_weights=False)
assert_("edge_weight" not in instance)

# Test Solomon instance
name = "solomon.txt"

with open(tmp_path / name, "w") as fi:
instance = "\n".join(SOLOMON_INSTANCE)
fi.write(instance)

instance = read_instance(tmp_path / name, "solomon", False)
assert_("edge_weight" not in instance)
9 changes: 7 additions & 2 deletions vrplib/parse/parse_solomon.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
Instance = Dict[str, Union[str, float, np.ndarray]]


def parse_solomon(text: str) -> Instance:
def parse_solomon(text: str, compute_edge_weights: bool = True) -> Instance:
"""
Parses the text of a Solomon VRPTW instance.
Parameters
----------
text
The instance text.
compute_edge_weights
Whether to compute the edge weights from the node coordinates.
Defaults to True.
Returns
-------
Expand All @@ -36,7 +39,9 @@ def parse_solomon(text: str) -> Instance:
instance["demand"] = data[:, 3]
instance["time_window"] = data[:, 4:6]
instance["service_time"] = data[:, 6]
instance["edge_weight"] = pairwise_euclidean(instance["node_coord"])

if compute_edge_weights:
instance["edge_weight"] = pairwise_euclidean(instance["node_coord"])

return instance

Expand Down
9 changes: 6 additions & 3 deletions vrplib/parse/parse_vrplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
Instance = Dict[str, Union[str, float, np.ndarray]]


def parse_vrplib(text: str) -> Instance:
def parse_vrplib(text: str, compute_edge_weights: bool = True) -> Instance:
"""
Parses a VRPLIB instance. An instance consists of two parts:
1) Specifications: single line of the form <KEY>:<VALUE>.
Expand All @@ -24,6 +24,9 @@ def parse_vrplib(text: str) -> Instance:
----------
text
The instance text.
compute_edge_weights
Whether to compute edge weights from the node coordinates.
Defaults to True.
Returns
-------
Expand All @@ -42,7 +45,7 @@ def parse_vrplib(text: str) -> Instance:
section, data = parse_section(section, instance)
instance[section] = data

if instance and "edge_weight" not in instance:
if instance and compute_edge_weights and "edge_weight" not in instance:
# Compute edge weights if there was no explicit edge weight section
edge_weights = parse_distances([], **instance) # type: ignore
instance["edge_weight"] = edge_weights
Expand Down Expand Up @@ -103,7 +106,7 @@ def parse_section(lines: List, instance: Dict) -> np.ndarray:

if section == "edge_weight":
# Parse separately because it may require additional processing
return section, parse_distances(data_, **instance)
return section, parse_distances(data_, **instance) # type: ignore

data = np.array(data_)

Expand Down
16 changes: 9 additions & 7 deletions vrplib/read/read_instance.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
from vrplib.parse import parse_solomon, parse_vrplib


def read_instance(path, instance_format="vrplib"):
def read_instance(path, instance_format="vrplib", compute_edge_weights=True):
"""
Reads the instance from the passed-in file path.
Parameters
----------
path
The path to the instance file.
style
The instance format, one of ['vrplib', 'solomon'].
instance_format
The instance format, one of ["vrplib", "solomon"]. Default is "vrplib".
compute_edge_weights
Whether to calculate edge weights based on instance specifications
and node coordinates, if not explicitly provided. Defaults to True.
Returns
-------
dict
The instance data.
A dictionary that contains the instance data.
"""
with open(path, "r") as fi:
if instance_format == "vrplib":
return parse_vrplib(fi.read())
return parse_vrplib(fi.read(), compute_edge_weights)
elif instance_format == "solomon":
return parse_solomon(fi.read())
return parse_solomon(fi.read(), compute_edge_weights)

raise ValueError(f"Format style {instance_format} not known.")

0 comments on commit d10da97

Please sign in to comment.