| 66 | = Extending an existing VRPEvaluator = |
| 67 | |
| 68 | In order to implement the ''Pollution-Routing Problem (PRP)'' we need to derive from an existing {{{VRPEvaluator}}}. We derive from the existing ''Capaciated-VRP (CVRP)'' solution because we need a capacity for each stop and vehicle. The new class {{{CPRPEvaluator}}} (Capacitated-PRP) derives from {{{CVRPEvaluator}}}. In addition we need a new {{{CPRPEvaluation}}} class, based on {{{CVRPEvaluation}}} and a new {{{CPRPInsertionInfo}}} class, based on {{{CVRPInsertionInfo}}}. |
| 69 | |
| 70 | [[Image(4-PRP_EvaluatorEvaluationInfo_Hierarchy.png, 1000px)]] |
| 71 | |
| 72 | == New CPRPEvaluation and CPRPInsertionInfo classes == |
| 73 | |
| 74 | The new {{{CPRPEvaluation}}} additionally stores the ''energy consumption'' of a solution candidate. For the energy consumption not only the driven distance is considered, but also the load of the vehicle. |
| 75 | |
| 76 | {{{ |
| 77 | #!cs |
| 78 | public class CPRPEvaluation : CVRPEvaluation { |
| 79 | public double EnergyConsumption { get; set; } |
| 80 | } |
| 81 | }}} |
| 82 | |
| 83 | In the {{{CPRPInsertionInfo}}} we additionally need to store the load and the driven distance of a vehicle after a stop. This information is used to implement the {{{GetTourInsertionCosts()}}} method for incremental evaluation later in the tutorial. |
| 84 | |
| 85 | {{{ |
| 86 | #!cs |
| 87 | public class CPRPInsertionInfo : CVRPInsertionInfo { |
| 88 | private readonly double load; |
| 89 | |
| 90 | public double Load { |
| 91 | get { return load; } |
| 92 | } |
| 93 | |
| 94 | private readonly double drivenDistance; |
| 95 | |
| 96 | public double DrivenDistance { |
| 97 | get { return drivenDistance; } |
| 98 | } |
| 99 | |
| 100 | public CPRPInsertionInfo(int start, int end, double spareCapacity, double load, double drivenDistance) |
| 101 | : base(start, end, spareCapacity) { |
| 102 | this.load = load; |
| 103 | this.drivenDistance = drivenDistance; |
| 104 | } |
| 105 | } |
| 106 | }}} |
| 107 | |
| 108 | == New CPRPEvaluator class == |
| 109 | |
| 110 | Now, we can start creating a new {{{CPRPEvaluator}}} class which overrides the {{{EvaluateTour()}}} and {{{GetTourInsertionCosts()}}} method. In addition it stores the {{{EnergyConsumptionParameter}}} and override a method for setting the default value for the parameter as well as override a method for set the parameter based on an evaluation. We also have to implement methods for creating a new evaluation object. Furthermore, the {{{CPRPEvaluator}}} has to implement a few HeuristicLab specific functionalities for cloning and persistence. For simplicity we left {{{EvaluateTour()}}} and {{{GetTourInsertionCosts()}}} blank for now and deal with them later. |
| 111 | |
| 112 | {{{ |
| 113 | #!cs |
| 114 | [Item("CPRPEvaluator", "Represents a single depot CPRP evaluator.")] |
| 115 | [StorableClass] |
| 116 | public class CPRPEvaluator : CVRPEvaluator { |
| 117 | public ILookupParameter<DoubleValue> EnergyConsumptionParameter { |
| 118 | get { return (ILookupParameter<DoubleValue>)Parameters["EnergyConsumption"]; } |
| 119 | } |
| 120 | |
| 121 | [StorableConstructor] |
| 122 | protected CPRPEvaluator(bool deserializing) : base(deserializing) { } |
| 123 | |
| 124 | public CPRPEvaluator() { |
| 125 | Parameters.Add(new LookupParameter<DoubleValue>("EnergyConsumption", "The energy consumption.")); |
| 126 | } |
| 127 | |
| 128 | public override IDeepCloneable Clone(Cloner cloner) { |
| 129 | return new CPRPEvaluator(this, cloner); |
| 130 | } |
| 131 | |
| 132 | protected CPRPEvaluator(CPRPEvaluator original, Cloner cloner) |
| 133 | : base(original, cloner) { |
| 134 | } |
| 135 | |
| 136 | protected override VRPEvaluation CreateTourEvaluation() { |
| 137 | return new CPRPEvaluation(); |
| 138 | } |
| 139 | |
| 140 | protected override void InitResultParameters() { |
| 141 | base.InitResultParameters(); |
| 142 | |
| 143 | EnergyConsumptionParameter.ActualValue = new DoubleValue(0); |
| 144 | } |
| 145 | |
| 146 | protected override void SetResultParameters(VRPEvaluation tourEvaluation) { |
| 147 | base.SetResultParameters(tourEvaluation); |
| 148 | |
| 149 | EnergyConsumptionParameter.ActualValue.Value = (tourEvaluation as CPRPEvaluation).EnergyConsumption; |
| 150 | } |
| 151 | |
| 152 | protected override void EvaluateTour(VRPEvaluation eval, IVRPProblemInstance instance, Tour tour, IVRPEncoding solution) { |
| 153 | //TODO |
| 154 | } |
| 155 | |
| 156 | protected override double GetTourInsertionCosts(IVRPProblemInstance instance, IVRPEncoding solution, TourInsertionInfo tourInsertionInfo, int index, int customer, |
| 157 | out bool feasible) { |
| 158 | //TODO |
| 159 | feasible = false; |
| 160 | return 0.0; |
| 161 | } |
| 162 | } |
| 163 | }}} |
| 164 | |
| 165 | == Implementing EvaluateTour == |
| 166 | |
| 167 | In order to implement {{{EvaluateTour()}}} for the {{{CPRPEvaluator}}} we copy the existing implementation from the {{{CVRPEvaluator}}} and modify it. This procedure, of copying an existing implementation instead of using proper extensible architecture, is done due to performance reasons. Because the evaluation process is triggered very often, a fast implementation is critical. The existing VRP variants are also based on copying existing solutions. |
| 168 | |
| 169 | In the beginning we rename the {{{delivered}}} variable to {{{load}}} due to more appropriate meaning of the variable (do not forget to modify it in the subsequent code). In addition we need to store the {{{energy}}} consumption of a tour. Then we use the existing code to calculate the load and check if for overweight already on this position, because we will modify the {{{load}}} variable later. The first lines should look like following: |
| 170 | {{{ |
| 171 | #!cs |
| 172 | TourInsertionInfo tourInfo = new TourInsertionInfo(solution.GetVehicleAssignment(solution.GetTourIndex(tour))); |
| 173 | eval.InsertionInfo.AddTourInsertionInfo(tourInfo); |
| 174 | double originalQuality = eval.Quality; |
| 175 | |
| 176 | IHomogenousCapacitatedProblemInstance cvrpInstance = instance as IHomogenousCapacitatedProblemInstance; |
| 177 | DoubleArray demand = instance.Demand; |
| 178 | |
| 179 | double load = 0.0; |
| 180 | double overweight = 0.0; |
| 181 | double distance = 0.0; |
| 182 | double energy = 0.0; |
| 183 | |
| 184 | double capacity = cvrpInstance.Capacity.Value; |
| 185 | for (int i = 0; i <= tour.Stops.Count; i++) { |
| 186 | int end = 0; |
| 187 | if (i < tour.Stops.Count) |
| 188 | end = tour.Stops[i]; |
| 189 | |
| 190 | load += demand[end]; |
| 191 | } |
| 192 | |
| 193 | double spareCapacity = capacity - load; |
| 194 | if (load > capacity) { |
| 195 | overweight = load - capacity; |
| 196 | } |
| 197 | }}} |
| 198 | |
| 199 | Now we can simulate a tour. In addition to the existing implementation we also calculate the used {{{energy}}} by multiplying the {{{currentDistance}}} with the current {{{load}}}. Afterwards we decrease the {{{load}}} by the {{{demand}}} of the current {{{stop}}}. Instead of the {{{CVRPInsertionInfo}}} we create a {{{CPRPInsertionInfo}}} and additionally add the current {{{load}}} and the summarized {{{distance}}}. |
| 200 | |
| 201 | {{{ |
| 202 | #!cs |
| 203 | //simulate a tour, start and end at depot |
| 204 | for (int i = 0; i <= tour.Stops.Count; i++) { |
| 205 | int start = 0; |
| 206 | if (i > 0) |
| 207 | start = tour.Stops[i - 1]; |
| 208 | int end = 0; |
| 209 | if (i < tour.Stops.Count) |
| 210 | end = tour.Stops[i]; |
| 211 | |
| 212 | //drive there |
| 213 | double currentDistace = instance.GetDistance(start, end, solution); |
| 214 | distance += currentDistace; |
| 215 | energy += currentDistace * load; |
| 216 | |
| 217 | load -= instance.GetDemand(end); |
| 218 | |
| 219 | CPRPInsertionInfo stopInfo = new CPRPInsertionInfo(start, end, spareCapacity, load, distance); |
| 220 | tourInfo.AddStopInsertionInfo(stopInfo); |
| 221 | } |
| 222 | }}} |
| 223 | |
| 224 | Finally, we must set the {{{EnergyConsumption}}} property of our {{{CPRPEvaluation}}} and add the used energy to the quality. |
| 225 | |
| 226 | {{{ |
| 227 | #!cs |
| 228 | eval.Quality += instance.FleetUsageFactor.Value; |
| 229 | eval.Quality += instance.DistanceFactor.Value * distance; |
| 230 | eval.Quality += energy; |
| 231 | |
| 232 | eval.Distance += distance; |
| 233 | eval.VehicleUtilization += 1; |
| 234 | (eval as CPRPEvaluation).EnergyConsumption += energy; |
| 235 | |
| 236 | (eval as CVRPEvaluation).Overload += overweight; |
| 237 | double penalty = overweight * cvrpInstance.OverloadPenalty.Value; |
| 238 | eval.Penalty += penalty; |
| 239 | eval.Quality += penalty; |
| 240 | tourInfo.Penalty = penalty; |
| 241 | tourInfo.Quality = eval.Quality - originalQuality; |
| 242 | }}} |
| 243 | |
| 244 | == Implementing GetTourInsertionCosts == |
| 245 | |
| 246 | In order to implement {{{GetTourInsertionCosts()}}} we copy the implementation from the {{{CVRPEvaluator}}} again. Instead of a {{{CVRPInsertionInfo}}} we want to use a {{{CPRPInsertionInfo}}}, therefore we change the types in the first line. |
| 247 | |
| 248 | To determine the insertion costs we have to consider the change of the load for the whole tour. In addition to the increased distance we have to consider following factors: |
| 249 | - The weight for {{{End}}} has to be carried the additional distance ({{{newDistance-distance}}}). |
| 250 | - The weight of the new {{{customer}}} has to be carried to the new {{{customer}}}. |
| 251 | - To {{{Start}}} ({{{drivenDistance-distance}}}) |
| 252 | - plus distance between {{{Start}}} and new {{{comstumer}}}. |
| 253 | |
| 254 | [[Image(5-TourInsertion.png, 500px)]] |
| 255 | |
| 256 | {{{ |
| 257 | #!cs |
| 258 | CPRPInsertionInfo insertionInfo = tourInsertionInfo.GetStopInsertionInfo(index) as CPRPInsertionInfo; |
| 259 | |
| 260 | double costs = 0; |
| 261 | feasible = tourInsertionInfo.Penalty < double.Epsilon; |
| 262 | |
| 263 | ICapacitatedProblemInstance cvrp = instance as ICapacitatedProblemInstance; |
| 264 | double overloadPenalty = cvrp.OverloadPenalty.Value; |
| 265 | |
| 266 | double distance = instance.GetDistance(insertionInfo.Start, insertionInfo.End, solution); |
| 267 | double newDistance = |
| 268 | instance.GetDistance(insertionInfo.Start, customer, solution) + |
| 269 | instance.GetDistance(customer, insertionInfo.End, solution); |
| 270 | costs += instance.DistanceFactor.Value * (newDistance - distance); |
| 271 | costs += (newDistance - distance) * (insertionInfo.Load + instance.GetDemand(insertionInfo.End)); |
| 272 | costs += (insertionInfo.DrivenDistance - distance) * instance.GetDemand(customer); |
| 273 | costs += instance.GetDistance(insertionInfo.Start, customer, solution) * instance.GetDemand(customer); |
| 274 | |
| 275 | double demand = instance.Demand[customer]; |
| 276 | if (demand > insertionInfo.SpareCapacity) { |
| 277 | feasible = false; |
| 278 | |
| 279 | if (insertionInfo.SpareCapacity >= 0) |
| 280 | costs += (demand - insertionInfo.SpareCapacity) * overloadPenalty; |
| 281 | else |
| 282 | costs += demand * overloadPenalty; |
| 283 | } |
| 284 | |
| 285 | return costs; |
| 286 | }}} |
| 287 | |