Simple example, adding behaviour - MayerTh/RVRPSimulator GitHub Wiki
We will be model behaviour for our model defined within the [simple example] (https://github.com/MayerTh/RVRPSimulator/wiki/Simple-example). RVRPSimulator manages behaviour with the help of the model element SolutionManager
. This manager organizes a static (IStaticBehaviourProvider
) and a dynamic (IDynamicBehaviourProvider
) behaviour provider. The static provider is mandatory and needed for instantiation. Since RVRPSimulator is not an VRP solving framework like for example [jsprit] (https://github.com/graphhopper/jsprit), RVRPSimulator is currently not providing any implementation of static or dynamic behaviour providers.
Before we will implement our own static behaviour provider, let's discuss fast what's the difference between the providers and how they work in general.
Is not mandatory to implement and is responsible to handle the dynamic parts of a vehicle routing problem. Means the implementation of public abstract class AbstractOrderManager implements IDynamicBehaviourProvider
functions as dispatcher in the system. The implementation gets informed if any OrderEvent
occurs during simulation and can react by rescheduling the vehicles/tours/drivers by full access to the network, structure, and event list services. RVRPSimulator provides several conveniences, which makes controlling the fleet easy and effective. For example there is the possibility to get informed through listeners if structure elements are getting allocated or released. Several more conveniences are implemented, and if you need more of them, just [ask] (https://github.com/MayerTh/RVRPSimulator/issues), and if needed, we can implement them. Following, one of the most important method an implementation of AbstractOrderManager
has to implemented.
protected final OrderBord orderBord;
protected StructureService structureService;
protected NetworkService networkService;
protected BehaviourService behaviourService;
protected EventListService eventListService;`
...
/**
* Handles the {@link OrderEvent} created from dynamic parts of the vehicle
* routing problem. Currently {@link DynamicCustomer}, an implementation of
* {@link ICustomer} creates {@link OrderEvent}. An implementation of
* {@link AbstractOrderManager} functions as dispatcher within the system.
* An implementation of {@link AbstractOrderManager} has the following
* possibilities to handle and {@link OrderEvent}:
*
* - Simply ignore it.
*
* - Publishing it at the {@link OrderBord}, after adding the following
* parameters to the {@link Order}:
* {@link Order#setProvider(vrpsim.core.model.structure.IVRPSimulationModelStructureElementWithStorage)}
* (where to pick up the ordered stuff?)
* {@link Order#setAdditionalCost(OrderCost)} (What the Dispatcher (
* {@link AbstractOrderManager}) is willing to pay for delivering the
* order.) The {@link AbstractOrderManager} gets informed through
* {@link IDynamicBehaviourProvider#handleNotTakenOrder(Order)}, if an
* published {@link Order} will not be handled from an
* {@link IOccasionalDriver} (or any thing else who can access the
* {@link OrderBord}). -> the parameters can now be readjusted, and the
* {@link Order} can be published again for example, or the own fleet can be
* used for delivery (see the next point).
*
* - Use the own fleet to deliver the order. Therefore access to the
* network, structure and event list is provided by
* {@link AbstractOrderManager#networkService},
* {@link AbstractOrderManager#structureService},
* {@link AbstractOrderManager#behaviourService}, and
* {@link AbstractOrderManager#eventListService}. Different other
* conveniences are implemented, see for example
* {@link IVRPSimulationModelStructureElement#addReleaseFromListener(java.util.Observer)}
* , {@link IVRPSimulationModelStructureElement#isAvailable(IClock)}, ...
*
* @param orderEvent
* @param simulationClock
*/
public abstract void handleOrderEvent(OrderEvent orderEvent, IClock simulationClock);
Since we don't have any dynamic parts in our [modeled vehicle routing problem] (https://github.com/MayerTh/RVRPSimulator/wiki/Simple-example), we don't have to implement a dynamic behaviour provider. But what we have to do is implementing a static behaviour provider, which is responsible for creating the initial/static behaviour of our model. So let us create the class StaticBehaviourProvider
implementing the interface IStaticBehaviourProvider
. The interfaces requires us to implement the following method:
public Behaviour provideBehavior(NetworkService networkService, StructureService structureService) {
// TODO Auto-generated method stub
return null;
}
Additionally we adapt our getSimulationModel()
method in SimpleModelGenerator
like following:
public VRPSimulationModel getSimulationModel() {
VRPSimulationModelParameters vrpSimulationModelParameters = new VRPSimulationModelParameters("Simple Example","Owner");
Network network = getNetwork();
Structure structure = getStructure(network);
VRPSimulationModel model = new VRPSimulationModel(vrpSimulationModelParameters, structure, network);
// Initiate our implemented behaviour and add it to the model, packed in
// the solution manager.
IStaticBehaviourProvider staticBehaviourProvider = new StaticBehaviourProvider()
SolutionManager soluationManager = new SolutionManager(staticBehaviourProvider);
model.setSolutionManager(soluationManager);
return model;
}
Now we can model the "behaviour" of our modeled vehicle to satisfy the customer demands in the method provideBehavior(...)
. So we will model following tour for our vehicle:
- Load 4 at the Depot1
- Transport 4 to Customer1 at Node1
- Unload 2 at Customer1
- Transport the remaining 2 to Customer2 at Node2
- Unload 2 at Customer2
- Drive back empty to the Depot1 at Node5
- Load 4 at the Depot1
- Transport 4 to Customer3 at Node3
- Unload 2 at Customer3
- Transport the remaining 2 to Customer4 at Node4
- Unload 2 at Customer4
Let's create now two more methods in our class StaticBehaviourProvider
which implements the interface IStaticBehaviourProvider
. First we create:
private IActivity getStartActivity(NetworkService networkService, StructureService structureService) {
...
}
This will be the method which builds our activities (loading, unloading, transporting). Activities are organized in a successor relationships, so our tour only needs a start activity. The second method we create is following:
private List<ITour> getTours(NetworkService networkService, StructureService structureService) {
IVehicle vehicle = structureService.getVehicle("Vehicle1");
IDriver driver = structureService.getDriver("Driver1");
TourContext context = new TourContext(new Clock.Time(0.0), vehicle, driver);
IActivity startActivity = getStartActivity(networkService, structureService);
ITour tour = new Tour(context, startActivity);
List<ITour> tours = new ArrayList<ITour>();
tours.add(tour);
return tours;
}
As you can see, in the method getTours(...)
we create a TourContext
first. The context is instantiated with the vehicle and the driver, organized with this tour, and with a start time. So we can generate tours at the beginning, starting later during the simulation. The tour itself is instantiated with this context and the start activity we get from the method getStartActivity(..)
. Before we have a closer look at this method, following the adapted provideBehavior(...)
method, where we now create the behaviour with the tour instance.
public Behaviour provideBehavior(NetworkService networkService, StructureService structureService) {
List<ITour> tours = getTours(networkService, structureService);
Behaviour behaviour = new Behaviour(tours);
return behaviour;
}
Now we have to create our activities. As you can see in the list before, there are only three kinds of activities/processes in the system. We have to transport, and we have to load/unload. That's it. The following lines of code show the implementation of a loading LoadActivity
, of a transport TransportActivity
and of an unloading UnloadActivity
.
LoadActivity activity1 = new LoadActivity(new LoadUnloadJob(storableParameters, 4, depot));
TransportActivity activity2 = new TransportActivity(new TransportJob(node1));
UnloadActivity activity3 = new UnloadActivity(new LoadUnloadJob(storableParameters, 2, customer1));
The activities are instantiated with an implementation of the interface IJob
. There are only two different jobs: LoadUnloadJob
, modelling parameters for loading and unloading, and TransportJob
, modelling parameters for transportation. In the following there is the complete code for the activity generation.
private IActivity getStartActivity(NetworkService networkService, StructureService structureService){
// Network and structure elements we need we are getting from the services.
StorableParameters storableParameters = structureService.getStorableparameters().get(0);
IDepot depot = structureService.getDepot("Depot1");
ICustomer customer1 = structureService.getCustomer("Customer1");
ICustomer customer2 = structureService.getCustomer("Customer2");
ICustomer customer3 = structureService.getCustomer("Customer3");
ICustomer customer4 = structureService.getCustomer("Customer4");
INode node1 = (INode) networkService.getNetworkElement("Node1");
INode node2 = (INode) networkService.getNetworkElement("Node2");
INode node3 = (INode) networkService.getNetworkElement("Node3");
INode node4 = (INode) networkService.getNetworkElement("Node4");
INode node5 = (INode) networkService.getNetworkElement("Node5");
// Load, unload and transport in the correct order.
LoadActivity activity1 = new LoadActivity(new LoadUnloadJob(storableParameters, 4, depot));
TransportActivity activity2 = new TransportActivity(new TransportJob(node1));
UnloadActivity activity3 = new UnloadActivity(new LoadUnloadJob(storableParameters, 2, customer1));
TransportActivity activity4 = new TransportActivity(new TransportJob(node2));
UnloadActivity activity5 = new UnloadActivity(new LoadUnloadJob(storableParameters, 2, customer2));
TransportActivity activity6 = new TransportActivity(new TransportJob(node5));
LoadActivity activity7 = new LoadActivity(new LoadUnloadJob(storableParameters, 4, depot));
TransportActivity activity8 = new TransportActivity(new TransportJob(node3));
UnloadActivity activity9 = new UnloadActivity(new LoadUnloadJob(storableParameters, 2, customer3));
TransportActivity activity10 = new TransportActivity(new TransportJob(node4));
UnloadActivity activity11 = new UnloadActivity(new LoadUnloadJob(storableParameters, 2, customer4));
// Setting the successor.
activity1.setSuccessor(activity2);
activity2.setSuccessor(activity3);
activity3.setSuccessor(activity4);
activity4.setSuccessor(activity5);
activity5.setSuccessor(activity6);
activity6.setSuccessor(activity7);
activity7.setSuccessor(activity8);
activity8.setSuccessor(activity9);
activity9.setSuccessor(activity10);
activity10.setSuccessor(activity11);
return activity1;
}
First we get all structure and network elements we need, then we generate the activities, and finally we set the successor relationships. Note, that the execution of an activity is updating the tour context and the involved elements (means the vehicle or driver), so you could create invalid activity sequences. For example you could transport to "Node1" and then load a customer located at "Node3". If you create such an invalid sequence, the simulation will terminate with an exception during the try to execute the invalid sequence. So currently there is no check of invalidity before the simulation. RVRPSimulator is not executing invalid sequences.
Note, that there are very more generic ways to access the vehicle routing problem parameters. For exmaple the orders from the customers can be accessed with the following method. All simultion model elements with storage are also implementing the interface IVRPSimulationModelStructureElementWithStorage
, which helps you to access information about free, maximum or current capacity of the element.
public interface IStaticCustomer extends ICustomer {
/**
* Returns all initial static orders.
*
* @return
*/
public List<Order> getStaticOrdersBeforeEventGeneration();
}
So let's run our project as Java Application again. We can now start the simulation, but first we will only execute the simulation step-wise, to observe the changes in the system. Executing two steps corrensponds to execute one activity. The first step prepares the activity (here we can expect exceptions, if we try to execute an invalid activity), the second step executes the activity. So let's execute two steps and see what happens:
![Behaviour, executed 2 steps.] (https://github.com/MayerTh/RVRPSimulator/blob/master/vrpsim-examples/example/07.behaviour1.png)
First an popup opens, this popup is visualizing the executed tour. It shows the current tour costs (distance), the simulation time and so on. It also shows the involved vehicle, and the loading (after executing the two steps, the vehicle carries 4 pizza-cartons). Let's execute another two steps.
![Behaviour, executed 6 steps.] (https://github.com/MayerTh/RVRPSimulator/blob/master/vrpsim-examples/example/07.behaviour2.png)
You can see how the vehicle and the driver move to "Node1" where the first customer is located. Than they exchange two pizza-cartons. The tour is updated regarding our modeled structure (remember: ITimeFunction
and IDistanceFunction
). After the whole tour is executed, you should see the following.
![Behaviour, execution finished.] (https://github.com/MayerTh/RVRPSimulator/blob/master/vrpsim-examples/example/07.behaviour3.png)
Note, that the customers consume their delivered pizza-cartons, before the simulation is finished. With the help of the log file, located in the root of your maven project, you can comprehend the simulation.