This feature is only available for Platform. You can find a list of all available features here.
This how-to guide assumes you already completed the get started with vehicle routing tutorial.
An objective is a soft rule that is used to evaluate the quality of a solution, by giving it a numeric value. It is also known as value function. In vehicle routing it is common to search for the solution that minimizes the total value of the objective. For example, you may want to minimize the total travel duration across all vehicles in a solution.
We offer the ModelObjectiveSum
as the main objective that is optimized by the solver. It is a linear sum of multiple objectives, each with a factor that determines its weight, as given by the following expression:
Where:
factor_i
is the weight of theobjective_i
.objective_i
is thei
-th objective and is represented by afloat
value.
This model_objective_sum
, by default, is composed of one objective. This objective is the travel duration of all the vehicles in the solution, using a factor of 1.0. You can always override this behavior using the -model.objectives.travelduration
option.
We offer many objectives that can be added to the model_objective_sum
out of the box, for example:
- Early arrival time penalty
- Late arrival time penalty
- Stop unplanned penalty
- Vehicle activation penalty
- Min stops objective
- Cluster objective
Of course, you may have your own objectives that are specific to your business needs. In this how-to guide, we will show you how to implement a custom objective.
The nextroute
Go package provides the ModelObjective
interface. To create a custom objective, you have to implement this interface with the specific business rules you want to model.
Once the custom type implements the ModelObjective
interface, you pass it to the Model
, by means of the Objective()
method. This method returns the ModelObjectiveSum
objective inteface. The interface offers the NewTerm
method which you can use to add as many objective terms as you want.
The most important methods of the ModelObjective
interface are EstimateDeltaValue
and Value
.
EstimateDeltaValue
returnsfloat
value that represents the estimated change in the score (the objective's value) if theMove
is executed on theSolution
. Note that executing a move not necessarily improves the score. An improvement is represented by a negative value being returned.Value
returns thefloat
value that represents the score for the given solution.Value
answers the question "what is the value of this solution?", whereasEstimateDeltaValue
answers the question "what would happen to the score if I were to execute this move on this solution?"
The methods make use of the following arguments:
Move
. A move in a solution. It is a potential change in the solution that can be executed, such as planning a stop on a vehicle or unplanning multiple stops from one. A move may or may not result in changing the solution, as it not always results in an improvement. We encourage you to read the package documentation for more information.Solution
. Pretty self-explanatory: a solution to a vehicle routing problem. A solution contains all the information about how the problem is solved, such as which stops are planned on which vehicles and in which order. A solution also contains the values for the different objectives that were given. We encourage you to read the package documentation for more information.
Let's see an example of implementing a custom objective.
Example
Let's say in addition to minimizing the travel duration, you want to balance the stops that are planned across vehicles. This is, you want to make the routes as even as possible as determined by the number of stops that are planned. Ideally, all vehicles have the same number of stops assigned.
Consider the following input, where we use the custom_data
field in the input
to define a penalty for our objective. The penalty will be used to balance out the value against the travel duration objective.
You can customize the main.go
file that you initialized in the get started with platform tutorial. The following code snippet shows how to implement the ModelObjective
interface to model the balance objective and how to pass it to the Model
:
Please consider the following.
- The
customObjective
struct
implements theModelObjective
interface because theEstimateDeltaValue
andValue
methods are defined on that type. - In the
solver
function, we unmarshal thecustom_data
defined on theinput
into thecustomObjective
struct
. We pass it to theModel
by means of theObjective().NewTerm()
method. For simplicity, a term of 1.0 is used. - The value of the balance objective will be calculated by looping over all the vehicles in the solution to find the maximum and minimum number of stops that are planned. The difference between the maximum and minimum, if minimized to zero, implies that all vehicles have the same number of stops assigned to them.
- The difference between the maximum and minimum stops is multiplied by the penalty read from the
custom_data
to balance out the value against the travel duration. Given that the travel duration may be several hundreds or thousands of seconds (depending on the problem of course), we want the balance objective to be on the same order of magnitude so that the solver has a proper incentive to distribute stops across vehicles. - In the
EstimateDeltaValue
method:- We first calculate the baseline of the objective by calculating the maximum and minimum number of stops that are planned across all vehicles in the current solution.
- In case the move's vehicle holds the minimum number of stops, executing that move will imply that the minum number of stops will not necessarily belong to that vehicle, given that it will increase the stops planned on it. For this reason, we calculate the second minimum number of stops.
- The delta is calculated by determining how the difference between maximum and minimum changes. The increase is multiplied by the penalty. If there is no change in the difference, the delta is zero.
- In the
Value
method:- We use the same logic to calculate the difference between the maximum and minimum number of stops and mutliply it by the penalty.
- We implement the
String() string
method on thecustomObjective
type to get a human-readable representation of the objective.
After running the code, you should get an output similar to this one:
Please note that the value of the balance_penalty
objective reflects that there is a difference of 1 stop between the two vehicles, given that the penalty is set to be 1000.
By comparison, this is the output of running the same problem without the balance_penalty
:
This solution shows that the custom objective is influential enough to the solver to worsen the travel duration, by balancing out the stops.