You are viewing Nextmv legacy docs. ⚡️ Go to latest docs ⚡️

Router

Selector

You will learn how to use the Selector option with a practical example.

By default the router engine looks in the list of stops yet to be assigned and selects the one with the lowest index. However, it may be useful to select the next stop to be inserted into a route differently, which means that the default behaviour needs to overridden.

The router engine provides the Selector option to set up a custom selector for stops. This is done by writing a function that selects the next stop or even multiple stops.

Example

The router example is used as a base, where routes are created to visit seven landmarks in Kyoto using two vehicles and a score for each stop. This time, we define a function in code, not in the input file, to create a custom selector.

Save the following information in an input.json file (see input and output for more information on working with input files).

{
  "stops": [
    {
      "id": "Fushimi Inari Taisha",
      "position": { "lon": 135.772695, "lat": 34.967146 }
    },
    {
      "id": "Kiyomizu-dera",
      "position": { "lon": 135.78506, "lat": 34.994857 }
    },
    {
      "id": "Nijō Castle",
      "position": { "lon": 135.748134, "lat": 35.014239 }
    },
    {
      "id": "Kyoto Imperial Palace",
      "position": { "lon": 135.762057, "lat": 35.025431 }
    },
    {
      "id": "Gionmachi",
      "position": { "lon": 135.775682, "lat": 35.002457 }
    },
    {
      "id": "Kinkaku-ji",
      "position": { "lon": 135.728898, "lat": 35.039705 }
    },
    {
      "id": "Arashiyama Bamboo Forest",
      "position": { "lon": 135.672009, "lat": 35.017209 }
    }
  ],
  "vehicles": ["v1", "v2"],
  "score": [5, 4, 6, 7, 3, 2, 1]
}
Copy

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 support@nextmv.io.

To proceed with running the example, create a main.go file and use the code snippet below. We create a function to select the next stop based on a score which we then pass into the Selector option.

package main

import (
    "github.com/nextmv-io/code/engines/route"
    fleetEngine "github.com/nextmv-io/code/engines/route/fleet"
    "github.com/nextmv-io/code/hop/model"
    "github.com/nextmv-io/code/hop/run/cli"
    "github.com/nextmv-io/code/hop/solve"
)

// Struct to read from JSON in.
type input struct {
    Stops    []route.Stop `json:"stops,omitempty"`
    Vehicles []string     `json:"vehicles,omitempty"`
    Score    []int        `json:"score,omitempty"`
}

// Use the CLI runner to solve a Vehicle Routing Problem.
func main() {
    f := func(i input, opt solve.Options) (solve.Solver, error) {
        // Define a location selector. This location selector looks for the highest
        // score of a stop among the not yet assigned stops and returns it, wrapped
        // in an IntDomain
        selector := func(s fleetEngine.State) model.IntDomain {
            index := -1
            highestScore := 0
            for _, l := range s.Locations().Slice() {
                if i.Score[l] > highestScore {
                    index = l
                    highestScore = i.Score[l]
                }
            }
            if index != -1 {
                return model.Domain(model.Range(index, index))
            }
            return model.Domain()
        }
        router, err := route.NewRouter(
            i.Stops,
            i.Vehicles,
            route.Selector(selector),
        )
        if err != nil {
            return nil, err
        }

        return router.Solver(opt)
    }

    cli.Run(f)
}
Copy

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).

go run main.go -hop.runner.input.path input.json | jq .state
Copy

Solution

The solution should look similar to this one:

{
  "unassigned": [],
  "vehicles": [
    {
      "id": "v1",
      "route": [
        {
          "id": "Kinkaku-ji",
          "position": {
            "lon": 135.728898,
            "lat": 35.039705
          }
        },
        {
          "id": "Nijō Castle",
          "position": {
            "lon": 135.748134,
            "lat": 35.014239
          }
        },
        {
          "id": "Kyoto Imperial Palace",
          "position": {
            "lon": 135.762057,
            "lat": 35.025431
          }
        },
        {
          "id": "Gionmachi",
          "position": {
            "lon": 135.775682,
            "lat": 35.002457
          }
        },
        {
          "id": "Kiyomizu-dera",
          "position": {
            "lon": 135.78506,
            "lat": 34.994857
          }
        },
        {
          "id": "Fushimi Inari Taisha",
          "position": {
            "lon": 135.772695,
            "lat": 34.967146
          }
        }
      ],
      "route_duration": 1242
    },
    {
      "id": "v2",
      "route": [
        {
          "id": "Arashiyama Bamboo Forest",
          "position": {
            "lon": 135.672009,
            "lat": 35.017209
          }
        }
      ],
      "route_duration": 0
    }
  ]
}
Copy

To find this solution, the highest priority stops were selected and assigned first.

Page last updated

Go to on-page nav menu