Custom constraints
You will learn how to use the Constraint option with a practical example.
The router engine works with common constraints in vehicle routing problems (VRPs) out of the box, such as capacity or limits. But some problems may need very specific constraints.
The router engine provides the Constraint option to add a custom constraint. This is done by defining a custom type that implements the Constraint interface of the vehicle engine. An instance of this type is then passed into the Constraint option with the vehicle IDs to which this constraint should be applied. It is possible to use the Constraint option multiple times to add multiple custom constraints.
Constraints make sure that when stops are being inserted into a route, the route is still feasible. Filters, however, are used to prevent assigning stops to a vehicle that they are incompatible with under any circumstance. It is often good practice to also make use of a filter when using a custom constraint.

Example

The aim of this example is to define a custom type to create a custom constraint. The introductory router example is used as a base, where routes are created to visit seven landmarks in Kyoto using two vehicles.
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
}
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. We create a custom type that implements the Constraint interface, which we then pass into the Constraint option together with a slice of vehicle IDs.
Go
1
package main
2
3
import (
4
"crypto/sha1"
5
"encoding/base64"
6
"fmt"
7
"io"
8
9
"github.com/nextmv-io/code/engines/route"
10
vehicleEngine "github.com/nextmv-io/code/engines/route/vehicle"
11
"github.com/nextmv-io/code/hop/run/cli"
12
"github.com/nextmv-io/code/hop/solve"
13
)
14
15
// Struct to read from JSON in.
16
type input struct {
17
Stops []route.Stop `json:"stops,omitempty"`
18
Vehicles []string `json:"vehicles,omitempty"`
19
}
20
21
// A custom type that implements Violated to fulfill the Constraint interface
22
type CustomConstraint struct {
23
count int
24
}
25
26
// Violated is one of the two methods that must be implemented to be used as a
27
// Constraint
28
func (c CustomConstraint) Violated(state vehicleEngine.State) (vehicleEngine.Constraint, bool) {
29
// Count only assigned stops, do not include start/end stops in count.
30
violated := len(state.Route())-2 > c.count
31
if violated {
32
return nil, true
33
}
34
return c, false
35
}
36
37
// Use the CLI runner to solve a Vehicle Routing Problem.
38
func main() {
39
f := func(i input, opt solve.Options) (solve.Solver, error) {
40
// Create a custom constraint.
41
constraint := CustomConstraint{count: 4}
42
router, err := route.NewRouter(
43
i.Stops,
44
i.Vehicles,
45
route.Constraint(constraint, []string{"v1", "v2"}),
46
)
47
if err != nil {
48
return nil, err
49
}
50
51
return router.Solver(opt)
52
}
53
54
cli.Run(f)
55
}
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
"vehicles": [
4
{
5
"id": "v1",
6
"route": [
7
{
8
"id": "Arashiyama Bamboo Forest",
9
"position": {
10
"lon": 135.672009,
11
"lat": 35.017209
12
}
13
},
14
{
15
"id": "Kinkaku-ji",
16
"position": {
17
"lon": 135.728898,
18
"lat": 35.039705
19
}
20
},
21
{
22
"id": "Nijō Castle",
23
"position": {
24
"lon": 135.748134,
25
"lat": 35.014239
26
}
27
},
28
{
29
"id": "Kyoto Imperial Palace",
30
"position": {
31
"lon": 135.762057,
32
"lat": 35.025431
33
}
34
}
35
],
36
"route_duration": 1085
37
},
38
{
39
"id": "v2",
40
"route": [
41
{
42
"id": "Fushimi Inari Taisha",
43
"position": {
44
"lon": 135.772695,
45
"lat": 34.967146
46
}
47
},
48
{
49
"id": "Kiyomizu-dera",
50
"position": {
51
"lon": 135.78506,
52
"lat": 34.994857
53
}
54
},
55
{
56
"id": "Gionmachi",
57
"position": {
58
"lon": 135.775682,
59
"lat": 35.002457
60
}
61
}
62
],
63
"route_duration": 448
64
}
65
]
66
}
Copied!
You can see that none of the given routes has more than four stops assigned due to the constraint which was applied to both vehicles v1 and v2.
Export as PDF
Copy link