IBM ILOG Dispatcher User's Manual > Field Service Solutions > CARP: Visiting Arcs Using Multiple Vehicles > Model

Once you have written a description of your problem, you can use Dispatcher classes to model it.

Step 2   -  

Open the example file

Open the example file YourDispatcherHome/examples/src/tutorial/carp_partial.cpp in your development environment.

As in the previous lessons, you will use a RoutingModel class to call the functions that create the dimensions, nodes, vehicles, and visits. For this example, RoutingModel also calls a function to create the graph that represents the road network.

Step 3   -  

Declare the RoutingModel class

Add the following code after the comment //Declare the RoutingModel class.

class RoutingModel {
  IloEnv              _env;
  IloModel            _mdl;
  IloDispatcherGraph  _graph;
  IloDimension2       _time;
  IloDimension2       _distance;
  IloDimension1       _weight;

  void addDimensions();
  void loadGraphInformation (char* arcFileName);
  void CreateIloNodes(char* nodeFileName, char* coordFileName);
  void CreateVehicles(char* vehicleFileName);
  void CreateVisits(char* visitsFileName);

An instance of IloDispatcherGraph is declared (_graph); this instance is used to create a road network of nodes. It can then be used to compute and store the cheapest paths between nodes based on the cost functions for any given vehicle.

The function loadGraphInformation is used to load and create the arcs and the arc costs that are stored in _graph.

The remaining code of RoutingModel is provided for you, and it calls a function (modifyGraphArcCost) that allows you to modify the cost of an arc within the code itself, rather than in a csv data file.

public:
  RoutingModel(IloEnv env, int argc, char* argv[]);
  ~RoutingModel() {}
  IloEnv    getEnv() const { return _env; }
  IloModel  getModel() const { return _mdl; }
  void modifyGraphArcCost();
  IloDispatcherGraph getGraph() const { return _graph; }
};

The function RoutingModel:addDimensions uses a shortest path calculation to represent the dimensions of time and distance in the graph network.

Step 4   -  

Create the distance functions

Add the following code after the comment //Create the distance functions.

  IloDistance SP_time =   IloGraphDistance (_graph);
  _time  =IloDimension2 (_env, SP_time, "time");
  _mdl.add(_time);

  IloDistance SP_distance = IloGraphDistance (_graph);
  _distance =IloDimension2 (_env, SP_distance, "distance");
  _mdl.add(_distance);
}

The shortest path time and distance functions (SP_time and SP_distance) are defined and added to the model (instead of the predefined functions IloEuclidian and IloManhatten that you have used before).

The next function creates the graph containing the arcs.

Step 5   -  

Load the graph information

Add the following code after the comment //Load the graph information

void RoutingModel::loadGraphInformation (char* arcFileName)    {
  _graph.createArcsFromFile (arcFileName);
  _graph.loadArcDimensionDataFromFile (arcFileName, _time);
  _graph.loadArcDimensionDataFromFile (arcFileName, _distance);
}

.

The member function IloDispatcherGraph::createArcsFromFile loads the road network data from a csv file, and creates the necessary nodes and arcs. The member function IloDispatcherGraph::loadArcDimensionDataFromFile loads the arc cost data from a csv file.

The function CreateIloNodes includes a command to associate the IloNodes to the graph nodes.

Step 6   -  

Create the nodes

Add the following code after the comment //Create the nodes.

    _graph.associateByCoordsInFile (node, coordFileName);

Instances of IloNode must be positioned within the graph. This can be done node-by-node with the method IloDispatcherGraph::setLocation, but this becomes impractical with even modestly sized networks. Instead, in this lesson you use the method associateByCoordsInFile to look up the coordinates of a given IloNode in a csv file, and then automatically associate the node to the graph node with matching coordinates.

Next, you create the vehicles with a cost function that uses a cost ratio.

Step 7   -  

Set the vehicle costs

Add the following code after the comment //Set the vehicle costs.

    vehicle.setCost(_time, 100 * costRatio);
    vehicle.setCost(_distance, 100 * (1- costRatio));
    vehicle.setCapacity(_weight, capacity);
    _mdl.add(vehicle);

The following line of code appears earlier in the CreateVehicles function:

    IloNum costRatio = line.getFloatByHeader("costRatio");

The costRatio is data from the vehicle csv file, and can be used, for example, to model vehicles that have different operating costs. Using different cost ratios for the different vehicles will change the solution paths; paths depend not only on the distance between nodes, but also on the way vehicle costs are calculated.

Next, the CreateVisits function adds the actual arc visits to the model.

Step 8   -  

Create the visits

Add the following code after the comment //Create the visits.

    IloNode node1 = IloNode::Find(_env, nodeName1);
    IloNode node2 = IloNode::Find(_env, nodeName2);

    IloVisit visit1(node1, node2, visitName);
    _mdl.add(visit1.getTransitVar(_weight) == quantity);
    _mdl.add(visit1.getDelayVar(_time) == timeValue );
    _mdl.add(visit1.getDelayVar(_distance) == distanceValue );
    _mdl.add(visit1);
    if(symmetric) {
      visit1.setPenaltyCost(0);
      char visitName2[16];
      sprintf(visitName2, "%s%c", visitName, c);
      IloVisit visit2(node2, node1, visitName2);
      _mdl.add(visit2.getTransitVar(_weight) == quantity);
      _mdl.add(visit2.getDelayVar(_time) == timeValue );
      _mdl.add(visit2.getDelayVar(_distance) == distanceValue );
      visit2.setPenaltyCost(0);
      _mdl.add(visit2);
      _mdl.add(visit1.performed() + visit2.performed() == 1);

This code is the second part of the createVisits function. The first part of the iteration reads in the nodes, time and distance values, and quantity to be delivered per visit.

To create an arc visit rather than a visit to a single node, you provide two node names to the IloVisit constructor. If the visit to the arc is symmetric--that is, the cost to visit the arc is the same regardless of which node is visited first--then both possible paths for the arc visit are added to the model with zero penalty cost.

The constraint _mdl.add(visit1.performed() + visit2.performed() == 1) states that just one of those two visits must be performed.

In problems of this type it may sometimes be desirable to be able to quickly modify the costs of certain paths, due to traffic or road conditions, experimental modeling, or other reasons. You may not want to edit your data files to model these factors; the function modifyGraphArcCost is used for this purpose.

Step 9   -  

Modify graph arc costs

void RoutingModel::modifyGraphArcCost() {
  IloDispatcherGraph::Arc arc1 = _graph.getArc(3916); //Arc from 1043 to 987
  IloDispatcherGraph::Arc arc2 = _graph.getArc(4134); //Arc from 1043 to 1042
  IloDispatcherGraph::Arc arc3 = _graph.getArc(4136); //Arc from 1043 to 1044
  IloDispatcherGraph::Arc arc4 = _graph.getArc(4137); //Arc from 1043 to 1099

  _graph.setArcCost(arc1, _time, 10);
  _graph.setArcCost(arc2, _time, 10);
  _graph.setArcCost(arc3, _time, 10);
  _graph.setArcCost(arc4, _time, 10);
}

Add the following code after the comment //Modify graph arc costs.

This function is used to quickly modify the cost of a few particular arcs. This may be appropriate if, for example, conditions merit a temporary increase in the cost of visiting an arc. The impact on the solution cost can be significant.