Route limits
You will learn how to use the Limits option with a practical example.
In vehicle routing problems (VRPs) it is sometimes required to limit routes. To limit the route length or duration the router engine offers the LimitDistances and LimitDurations options. For all other use cases it provides the Limits option to configure limits for vehicles. This is done by setting a list of values and custom measures that must not be exceeded. The values must be provided in the same unit as the underlying measure for the vehicles. In addition, a flag must be specified to ignore or adhere to the triangle inequality.

Example

The aim of this example is to define custom limits for vehicles and an unassigned penalty to be able to receive a solution. The introductory router example is used as a base, where routes are created to visit seven landmarks in Kyoto using two vehicles.
limit-input
Save the following information in an input.json file (see input and output for more information on working with input files).
JSON
1
{
2
"stops": [
3
{
4
"id": "Fushimi Inari Taisha",
5
"position": { "lon": 135.772695, "lat": 34.967146 }
6
},
7
{
8
"id": "Kiyomizu-dera",
9
"position": { "lon": 135.78506, "lat": 34.994857 }
10
},
11
{
12
"id": "Nijō Castle",
13
"position": { "lon": 135.748134, "lat": 35.014239 }
14
},
15
{
16
"id": "Kyoto Imperial Palace",
17
"position": { "lon": 135.762057, "lat": 35.025431 }
18
},
19
{
20
"id": "Gionmachi",
21
"position": { "lon": 135.775682, "lat": 35.002457 }
22
},
23
{
24
"id": "Kinkaku-ji",
25
"position": { "lon": 135.728898, "lat": 35.039705 }
26
},
27
{
28
"id": "Arashiyama Bamboo Forest",
29
"position": { "lon": 135.672009, "lat": 35.017209 }
30
}
31
],
32
"vehicles": ["v1", "v2"],
33
"ignore_triangularity": true,
34
"penalties": [2000000, 2000000, 2000000, 2000000, 2000000, 2000000, 2000000]
35
}
Copied!

Code

The following program uses the CLI Runner to obtain a solution and requires access to the Nextmv code repository on GitHub. To request access, please contact [email protected].
To proceed with running the example, create a main.go file and use the code snippet below. Firstly, custom measures are created through the measures() function. These measures are simple and use the Haversine distance. With the measures created, the limits for the routes are defined by setting a maximum value of 9000. Because the Haversine measure is a distance measure, the value limits the route's length. Lastly, the limits are used to restrict the routes by using the Limits option.
Go
1
package main
2
3
import (
4
"github.com/nextmv-io/code/engines/measure"
5
"github.com/nextmv-io/code/engines/route"
6
"github.com/nextmv-io/code/hop/run/cli"
7
"github.com/nextmv-io/code/hop/solve"
8
)
9
10
// Struct to read from JSON in.
11
type input struct {
12
Stops []route.Stop `json:"stops,omitempty"`
13
Vehicles []string `json:"vehicles,omitempty"`
14
IgnoreTriangularity bool `json:"ignore_triangularity,omitempty"`
15
Penalties []int `json:"penalties,omitempty"`
16
}
17
18
// Use the CLI runner to solve a Vehicle Routing Problem.
19
func main() {
20
f := func(i input, opt solve.Options) (solve.Solver, error) {
21
// Create a Limit slice
22
limits := make([]route.Limit, len(i.Vehicles))
23
measures := measures(i)
24
for i, m := range measures {
25
limits[i] = route.Limit{
26
Measure: m,
27
Value: 9000,
28
}
29
}
30
31
router, err := route.NewRouter(
32
i.Stops,
33
i.Vehicles,
34
route.Limits(limits, i.IgnoreTriangularity),
35
route.Unassigned(i.Penalties),
36
)
37
if err != nil {
38
return nil, err
39
}
40
41
return router.Solver(opt)
42
}
43
44
cli.Run(f)
45
}
46
47
// measures returns an array of Haversine measures based on stops.
48
func measures(i input) []measure.ByIndex {
49
count := len(i.Stops)
50
points := make([]measure.Point, count+2*len(i.Vehicles))
51
52
for s, stop := range i.Stops {
53
points[s] = measure.Point{stop.Position.Lon, stop.Position.Lat}
54
}
55
56
// Haversine measure.
57
measures := make([]measure.ByIndex, len(i.Vehicles))
58
m := measure.Indexed(measure.HaversineByPoint(), points)
59
60
for v := range i.Vehicles {
61
measures[v] = m
62
}
63
64
return measures
65
}
Copied!
To execute the example, specify the path to the input.json file using command-line flags and use jq to extract the solution state (see runners for more information on building and running programs).
Bash
1
go run main.go -hop.runner.input.path input.json | jq .state
Copied!

Solution

The solution should look similar to this one:
JSON
1
{
2
"unassigned": [
3
{
4
"id": "Arashiyama Bamboo Forest",
5
"position": {
6
"lon": 135.672009,
7
"lat": 35.017209
8
}
9
}
10
],
11
"vehicles": [
12
{
13
"id": "v1",
14
"route": [
15
{
16
"id": "Kinkaku-ji",
17
"position": {
18
"lon": 135.728898,
19
"lat": 35.039705
20
}
21
},
22
{
23
"id": "Nijō Castle",
24
"position": {
25
"lon": 135.748134,
26
"lat": 35.014239
27
}
28
},
29
{
30
"id": "Kyoto Imperial Palace",
31
"position": {
32
"lon": 135.762057,
33
"lat": 35.025431
34
}
35
}
36
],
37
"route_duration": 510
38
},
39
{
40
"id": "v2",
41
"route": [
42
{
43
"id": "Fushimi Inari Taisha",
44
"position": {
45
"lon": 135.772695,
46
"lat": 34.967146
47
}
48
},
49
{
50
"id": "Kiyomizu-dera",
51
"position": {
52
"lon": 135.78506,
53
"lat": 34.994857
54
}
55
},
56
{
57
"id": "Gionmachi",
58
"position": {
59
"lon": 135.775682,
60
"lat": 35.002457
61
}
62
}
63
],
64
"route_duration": 448
65
}
66
]
67
}
Copied!
You can see that one stop is unassigned in the solution. Because of the route's limitation, it was not possible to add it to either of the two routes anymore.
limit-output
Export as PDF
Copy link