package com.voyager.opt.metaheuristics.gap;importlombok.Getter;importjava.util.Arrays;importjava.util.Random;importjava.util.stream.IntStream;@Getterpublicfinalclass GapSolution {/*** reference to the instance to be solved*/privatefinal GapInstance instance;/*** dimension:1* numTasks* assigned agent index for each task*/privatefinalint[] agentAssignments;/*** dimension:1* numAgents* consumed capacity of each agent*/privatefinalint[] consumedCapacities;/*** total objective value*/privateint objective;/*** assignment cost, without penalties*/privateint assignmentCost;/*** capacity violation penalties of all agents*/privateint capacityViolationPenalty;publicGapSolution(GapInstance instance){this.instance= instance;this.agentAssignments=newint[this.instance.getNumTasks()];this.consumedCapacities=newint[this.instance.getNumAgents()];Arrays.fill(this.agentAssignments,0);Arrays.fill(consumedCapacities,0);this.objective=0;this.assignmentCost=0;this.capacityViolationPenalty=0;}/*** copy constructor * @paramotherthe other solution to copy from*/publicGapSolution(GapSolution other){this.instance= other.instance;this.agentAssignments=newint[this.instance.getNumTasks()];System.arraycopy(other.agentAssignments,0,this.agentAssignments,0,this.instance.getNumTasks());this.consumedCapacities=newint[this.instance.getNumAgents()];System.arraycopy(other.consumedCapacities,0,this.consumedCapacities,0,this.instance.getNumAgents());this.objective= other.objective;this.assignmentCost= other.assignmentCost;this.capacityViolationPenalty= other.capacityViolationPenalty;}/*** randomly assign tasks to agents * @paramrandomrandom number generator*/publicvoidinitialize(Random random){int[][] resources =this.instance.getResources();for(int i =0; i < instance.getNumTasks(); i++){int agentIdx = random.nextInt(instance.getNumAgents());this.agentAssignments[i]= agentIdx;this.consumedCapacities[agentIdx]+= resources[agentIdx][i];}}/*** compute objective values * @paramcapacityViolationPenaltypenalty factor*/publicvoidcomputeObjective(int capacityViolationPenalty){// compute assignment coststhis.assignmentCost= IntStream.range(0, instance.getNumTasks()).map(taskIdx -> instance.getCosts()[agentAssignments[taskIdx]][taskIdx]).sum();// compute capacity violation coststhis.capacityViolationPenalty= IntStream.range(0, instance.getNumAgents()).map(agentIdx -> capacityViolationPenalty *Math.max(0,this.consumedCapacities[agentIdx]- instance.getCapacities()[agentIdx])).sum();this.objective=this.assignmentCost+this.capacityViolationPenalty;}publicintgetAssignedAgent(int taskIdx){returnthis.agentAssignments[taskIdx];}/*** assign agent to task * @paramtaskIdxthe task to be assigned*@paramagentIdxthe agent index*/publicvoidsetAssignedAgent(int taskIdx,int agentIdx){int currAgentIdx =this.agentAssignments[taskIdx];this.agentAssignments[taskIdx]= agentIdx;this.consumedCapacities[currAgentIdx]-= instance.getResources()[currAgentIdx][taskIdx];this.consumedCapacities[agentIdx]+= instance.getResources()[agentIdx][taskIdx];}}
Listing 7.2: Simulated annealing implementation
GapSimulatedAnnealing.java
package com.voyager.opt.metaheuristics.gap.sa;importcom.voyager.opt.metaheuristics.gap.GapInstance;importcom.voyager.opt.metaheuristics.gap.GapInstanceReader;importcom.voyager.opt.metaheuristics.gap.GapSolution;importcom.voyager.opt.metaheuristics.utils.PerfRecord;importcom.voyager.opt.metaheuristics.utils.PerfRecordsWriter;importlombok.Getter;importlombok.Setter;importjava.io.File;importjava.io.IOException;importjava.net.URISyntaxException;importjava.util.ArrayList;importjava.util.List;importjava.util.Random;@Getter@Setterpublicclass GapSimulatedAnnealing {/*** instance to be solved*/privatefinal GapInstance instance;/*** random number generator*/privatefinalRandom random;/*** best solution*/private GapSolution bestSolution;/*** performance records*/privateList<PerfRecord<Integer>> perfRecords;publicGapSimulatedAnnealing(GapInstance instance){this.instance= instance;this.random=newRandom(42);this.bestSolution=null;this.perfRecords=newArrayList<>();}publicvoidsolve(){// penalty factor for capacity violationint capacityViolationPenalty =1000;double initialTemperature =1000;double coolingRate =0.9999;double endingTemperature =0.0001;int iterationsPerTemperature =100;// create a starting solution GapSolution currSolution =newGapSolution(this.instance); currSolution.initialize(this.random); currSolution.computeObjective(capacityViolationPenalty);this.bestSolution= currSolution;this.perfRecords.add(new PerfRecord<>(0, currSolution.getObjective(), bestSolution.getObjective()));int numTasks =this.instance.getNumTasks();int numAgents =this.instance.getNumAgents();// Set initial temperaturedouble temperature = initialTemperature;while(temperature > endingTemperature){System.out.println("temperature: "+ temperature +", curr_obj: "+ currSolution.getObjective()+", best_obj: "+ bestSolution.getObjective());for(int i =0; i < iterationsPerTemperature; i++){// Generate neighbor solution GapSolution newSolution =newGapSolution(currSolution);// mutate one task assignmentint randTaskIdx =this.random.nextInt(numTasks);int currAgentIdx = newSolution.getAssignedAgent(randTaskIdx);int newAgentIdx =this.random.nextInt(numAgents);while(newAgentIdx == currAgentIdx){ newAgentIdx =this.random.nextInt(numAgents);} newSolution.setAssignedAgent(randTaskIdx, newAgentIdx);// compute objective value after mutation newSolution.computeObjective(capacityViolationPenalty);// Calculate cost differencesint deltaCost = newSolution.getObjective()- currSolution.getObjective();// Accept or reject neighbor solution based on Metropolis criterionif(deltaCost <0||Math.exp(-deltaCost / temperature)> random.nextDouble()){ currSolution = newSolution;}// Update best assignmentif(currSolution.getObjective()< bestSolution.getObjective()){ bestSolution = currSolution;}}// Cool down temperature temperature *= coolingRate;}}publicstaticvoidmain(String[] args)throwsIOException,URISyntaxException{File file =newFile("src/main/resources/data/gap/gap1.txt");String filePath = file.getAbsolutePath();String outputFilename ="/Users/klian/dev/books/metaheuristics-java/data/gap/perf_records_sa.csv";List<GapInstance> instances = GapInstanceReader.read(filePath); GapInstance instance = instances.get(1); GapSimulatedAnnealing simulatedAnnealing =newGapSimulatedAnnealing(instance); simulatedAnnealing.solve();// save performance records PerfRecordsWriter.write(outputFilename, simulatedAnnealing.getPerfRecords());}}
7.3 Performance Illustration
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltdf = pd.read_csv("/Users/klian/dev/books/metaheuristics-java/data/gap/perf_records_sa.csv", names=['iteration', 'curr_obj', 'best_obj'])df