diff --git a/README.md b/README.md index d894bc1d9b395db0e7e8580b9d248ba349948973..a305b5fe4b400c77275ce0365fd75a9f9376a87d 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ If you use the code from this repository, please cite publisher = {}, } ``` -For questions, please contact elina.ronnberg@liu.se +For questions, please contact lukas.eveborn@liu.se ### Electric Vehicle Routing Problem with Time Windows and Piecewise Linear Charging Function The instances that we use were introduced by M. Schneider, A. Stenger and D. Goeke *The Electric Vehicle-Routing Problem with Time Windows and Recharging Stations*, and can be found at https://data.mendeley.com/datasets/h3mrm5dhxw/1 -These instances are based on the benchmark indtances for the VRP of M.M. Solomon *Algorithms for the Vehicle Routing and Scheduling Problems with Time Window Constraints*. +These instances are based on the benchmark instances for the VRP of M.M. Solomon *Algorithms for the Vehicle Routing and Scheduling Problems with Time Window Constraints*. ## Code The code that was used in the paper is found in this repository. diff --git a/src/evrp_data_subproblem.h b/src/evrp_data_subproblem.h index 9ed16b564d5ca9b6d80c9c0f7e25ba7393b8632e..1614aa11bb3686aa8dfe3dfb356938b9fd8899b0 100644 --- a/src/evrp_data_subproblem.h +++ b/src/evrp_data_subproblem.h @@ -34,7 +34,9 @@ struct Node double min_time_arc; double min_fuel_arc; - double min_time_incoming; + double min_time_incoming_ng; + double min_time_outgoing_ng; + }; struct Arc diff --git a/src/labelling_algorithm.cpp b/src/labelling_algorithm.cpp index 9e68e2ff1a428cc32cd30393311eb122b05fb2ad..8e5a38014f222385a2123a95686085ac5da097be 100644 --- a/src/labelling_algorithm.cpp +++ b/src/labelling_algorithm.cpp @@ -671,16 +671,6 @@ void LabellingAlgorithm::assign_min_arc() least_arc_costs.push_back(make_tuple(nodes.uniqueNodeIndex, cost / nodeInfo[nodes.uniqueNodeIndex].demand)); least_arc_time.push_back(make_tuple(nodes.uniqueNodeIndex, cost / (time + (refuel / charging_rates[0])))); - // Incoming arcs to compute multiplicity of visits in ng-completion - time = 1000; - for (auto incoming : nodes.incomingNodes) - { - if (nodeInfo[incoming].nodeType != 'f') - { - time = min(time, arcInfo[incoming][nodes.uniqueNodeIndex].time + nodeInfo[incoming].serviceTime); - } - } - nodeInfo[nodes.uniqueNodeIndex].min_time_incoming = time; } sort(least_arc_costs.begin(), least_arc_costs.end(), compare_cost); // sort the queues cost/demand and cost/(time+refueltime) in increasing order (most negative value first) sort(least_arc_time.begin(), least_arc_time.end(), compare_cost); @@ -1049,6 +1039,28 @@ void LabellingAlgorithm::get_ng_sets(int size) closest.push_back(node.uniqueNodeIndex); // insert node itself into ints ng-set ng_set[node.uniqueNodeIndex] = closest; } + + + //find the node k closest to node i, such that i is not in the ng-set of k + for(auto node: nodeInfo){ + double closest_incoming=1000000; + double closest_outgoing=1000000; + for(auto next: nodeInfo){ //check all arcs + if(next.nodeType=='f'){ + continue; + } + else{ + int neighbour=next.uniqueNodeIndex; + if(find(ng_set[neighbour].begin(),ng_set[neighbour].end(),node.uniqueNodeIndex)==ng_set[neighbour].end()){ + closest_incoming=min(closest_incoming,arcInfo[node.uniqueNodeIndex][neighbour].time+nodeInfo[neighbour].serviceTime); + closest_outgoing=min(closest_outgoing,arcInfo[node.uniqueNodeIndex][neighbour].time+nodeInfo[node.uniqueNodeIndex].serviceTime); + } + } + } + node.min_time_incoming_ng=closest_incoming; + node.min_time_outgoing_ng=closest_outgoing; + } + } /** @@ -1155,13 +1167,7 @@ double LabellingAlgorithm::get_capacity_bound(Label* label, bool ng) double cost=nodeInfo[labelNode].min_cost_arc; double resource_left=maxResource.capacity-label->capacity; - double earliest_start_time=label->tMin+nodeInfo[labelNode].serviceTime; - - vector<tuple<int,int,int>> visited_in_completion; //node identifier, multiple of visits, maximum visits - visited_in_completion.reserve(20); - - int i=0; while(i<least_arc_costs.size()){ int currNode=get<0>(least_arc_costs[i]); @@ -1170,67 +1176,21 @@ double LabellingAlgorithm::get_capacity_bound(Label* label, bool ng) continue; } if(label->visitedForDominance[currNode]==0 || ng){ //node has not been visited before - if(ng){ - //add nodes to completion calculate the number of times it can be visited - int completion_size=visited_in_completion.size(); - if(completion_size<2){ //special case when adding the first two nodes - if(earliest_start_time+arcInfo[labelNode][currNode].time>nodeInfo[currNode].dueDate){ - i++; //find first customer node that is reachable - continue; - } - if(completion_size==0 && label->visitedForDominance[currNode]==1){ //if first node in completion, and label remembers first node - int k=i+1; //find best node that is not remembered and can be reached - while(k<least_arc_costs.size()){ - if(label->visitedForDominance[get<0>(least_arc_costs[k])]==0 && earliest_start_time+arcInfo[labelNode][get<0>(least_arc_costs[k])].time<=nodeInfo[get<0>(least_arc_costs[k])].dueDate){ - currNode=get<0>(least_arc_costs[k]); //k is reachable, not remebered by the label, and not a charging node - i=i-1; //the recent i was not added in the completion, but is valid - break; - } - else{ - k++; - } - } - } - - double start_time=max(earliest_start_time+arcInfo[labelNode][currNode].time,nodeInfo[currNode].readyTime); - int multiplicity=ceil((nodeInfo[currNode].dueDate-start_time)/(nodeInfo[currNode].min_time_arc+nodeInfo[currNode].min_time_incoming)); - visited_in_completion.emplace_back(make_tuple(currNode,1,multiplicity)); - i++; - - } - else{ - tuple<int,int,int> node=visited_in_completion[completion_size-2]; - if(get<1>(node)<get<2>(node)){ //check if any node can still be revisited. Reachability must not be tested here - visited_in_completion.emplace_back(make_tuple(get<0>(node), get<1>(node)+1,get<2>(node))); - currNode=get<0>(node); - } - else if(earliest_start_time+arcInfo[labelNode][currNode].time>nodeInfo[currNode].dueDate){ //verify reachability of new node - i++; - continue; - } - else{ - double start_time=max(earliest_start_time+arcInfo[labelNode][currNode].time,nodeInfo[currNode].readyTime); //add new node - int multiplicity=ceil((nodeInfo[currNode].dueDate-start_time)/(nodeInfo[currNode].min_time_arc+nodeInfo[currNode].min_time_incoming)); - visited_in_completion.emplace_back(make_tuple(currNode,1,multiplicity)); - i++; - } - } - } - if(nodeInfo[currNode].min_cost_arc>=0){ - if(ng){ - if(visited_in_completion.size()!=1){ //the first added node may have positive cost - break; - } - } - else{ - break; - } + break; } + double multiplicity=1; + if(ng){ //must check if it can be reached! + double start_time=max(earliest_start_time+arcInfo[labelNode][currNode].time,nodeInfo[currNode].readyTime); + if(start_time>nodeInfo[currNode].dueDate){ + i++; + continue; + } + multiplicity=floor((nodeInfo[currNode].dueDate-start_time)/(nodeInfo[currNode].min_time_outgoing_ng+nodeInfo[currNode].min_time_incoming_ng))+1; + } float value; - value=min((double)1,resource_left/nodeInfo[currNode].demand); - + value=min((double)multiplicity,resource_left/nodeInfo[currNode].demand); resource_left=resource_left-(nodeInfo[currNode].demand*value); cost=cost+(nodeInfo[currNode].min_cost_arc*value); @@ -1239,18 +1199,14 @@ double LabellingAlgorithm::get_capacity_bound(Label* label, bool ng) } } - if(!ng){ - i++; - } - } - if(ng){ - return(min(cost,nodeInfo[labelNode].min_cost_arc)); - } - else{ - return(cost); + i++; } + + return(cost); } + + /** * \fn time_bound_special * \brief adapted knapsack time bounds with battery constraints @@ -1269,13 +1225,11 @@ double LabellingAlgorithm::get_time_bound(Label* label, bool ng) } double resource_left=maxResource.tMin-label->tMin-nodeInfo[labelNode].min_time_arc-added_time; //reduce time availability - double earliest_start_time=label->tMin+nodeInfo[labelNode].serviceTime; double added_charging; double required_charging; - vector<tuple <int,int,int>> visited_in_completion; //node identifier, multiplicity of visits, maximum visits - visited_in_completion.reserve(15); + int i=0; while(i<least_arc_time.size()){ @@ -1286,89 +1240,33 @@ double LabellingAlgorithm::get_time_bound(Label* label, bool ng) continue; } else if(label->visitedForDominance[currNode]==0 || ng){ - if(ng){ - // add nodes to completion and calculate the number of times it can be visited - int completion_size=visited_in_completion.size(); - - if(completion_size<2){ //special case for first two nodes int the completion - if(label->tMin+nodeInfo[labelNode].serviceTime+arcInfo[labelNode][currNode].time>nodeInfo[currNode].dueDate){ - i++; //find first customer node in list that is reachable - continue; - } - if(completion_size==0 && label->visitedForDominance[currNode]==1){ //first node in completion may not be rememebered byt the label - int k=i+1; - while(k<least_arc_time.size()){ - if(label->visitedForDominance[get<0>(least_arc_time[k])]==0 && label->tMin+nodeInfo[labelNode].serviceTime+arcInfo[labelNode][get<0>(least_arc_time[k])].time<=nodeInfo[get<0>(least_arc_time[k])].dueDate && nodeInfo[get<0>(least_arc_time[k])].nodeType!='f'){ - currNode=get<0>(least_arc_time[k]); - i=i-1; //i is valid, and has not been added to completion - break; - } - else{ - k++; - } - } - } - double start_time=max(earliest_start_time+arcInfo[labelNode][currNode].time,nodeInfo[currNode].readyTime); - int multiplicity=ceil((nodeInfo[currNode].dueDate-start_time)/(nodeInfo[currNode].min_time_arc+nodeInfo[currNode].min_time_incoming)); - visited_in_completion.emplace_back(make_tuple(currNode,1,multiplicity)); - i++; - } - else{ - tuple<int,int,int>node=visited_in_completion[completion_size-2]; - - if(get<1>(node)<get<2>(node)){ //check second to last node can still be revisited. Reachability must not be tested - visited_in_completion.emplace_back(make_tuple(get<0>(node), get<1>(node)+1,get<2>(node))); - currNode=get<0>(node); - - } - else if(label->tMin+nodeInfo[labelNode].serviceTime+arcInfo[labelNode][currNode].time>nodeInfo[currNode].dueDate){ - i++; //else find next node that is reachable - continue; - } - else{ //add node to completion of the path - double start_time=max(earliest_start_time+arcInfo[labelNode][currNode].time,nodeInfo[currNode].readyTime); - int multiplicity=ceil((nodeInfo[currNode].dueDate-start_time)/(nodeInfo[currNode].min_time_arc+nodeInfo[currNode].min_time_incoming)); - visited_in_completion.emplace_back(make_tuple(currNode,1,multiplicity)); - i++; - } - } - } - - if(nodeInfo[currNode].min_cost_arc>=0){ - if(ng){ - if(visited_in_completion.size()!=1){ //the first added node may have positive cost - break; - } - } - else{ - break; + + if(nodeInfo[currNode].min_cost_arc>=0){ + break; + } + + double multiplicity=1; + if(ng){ + double start_time=max(earliest_start_time+arcInfo[labelNode][currNode].time,nodeInfo[currNode].readyTime); + if(start_time>nodeInfo[currNode].dueDate){ + i++; + continue; } + multiplicity=floor((nodeInfo[currNode].dueDate-start_time)/(nodeInfo[currNode].min_time_outgoing_ng+nodeInfo[currNode].min_time_incoming_ng))+1; } - float value; - added_charging=max((double)0,nodeInfo[currNode].min_fuel_arc-energy)/charging_rates[0]; //charging time at fastest rate energy=max((double)0.0,energy-nodeInfo[currNode].min_fuel_arc); //update the energy resource - value=min((double)1,resource_left/(nodeInfo[currNode].min_time_arc+added_charging)); + value=min((double)multiplicity,resource_left/(nodeInfo[currNode].min_time_arc+added_charging)); resource_left=resource_left-(nodeInfo[currNode].min_time_arc+added_charging)*value; - cost=cost+(nodeInfo[currNode].min_cost_arc*value); if(resource_left<=0){ break; - } - + } } - if(!ng){ - i++; - } - } - - if(ng){ - return(min(cost,nodeInfo[labelNode].min_cost_arc)); - } - else{ - return(cost); + i++; } + return(cost); }