/**
*
* @author http://v...content-available-to-author-only...y.com
* Use this code at your own risk ;)
*/
class TabuSearch {
public static int[] getBestNeighbour(TabuList tabuList,
TSPEnvironment tspEnviromnet,
int[] initSolution) {
int[] bestSol = new int[initSolution.length]; //this is the best Solution So Far
System.
arraycopy(initSolution,
0, bestSol,
0, bestSol.
length); int bestCost = tspEnviromnet.getObjectiveFunctionValue(initSolution);
int city1 = 0;
int city2 = 0;
boolean firstNeighbor = true;
for (int i = 1; i < bestSol.length - 1; i++) {
for (int j = 2; j < bestSol.length - 1; j++) {
if (i == j) {
continue;
}
int[] newBestSol = new int[bestSol.length]; //this is the best Solution So Far
System.
arraycopy(bestSol,
0, newBestSol,
0, newBestSol.
length); newBestSol = swapOperator(i, j, initSolution); //Try swapping cities i and j
printSolution(newBestSol);
// , maybe we get a bettersolution
int newBestCost = tspEnviromnet.getObjectiveFunctionValue(newBestSol);
if ((newBestCost > bestCost || firstNeighbor) && tabuList.tabuList[i][j] == 0) { //if better move found, store it
firstNeighbor = false;
city1 = i;
city2 = j;
System.
arraycopy(newBestSol,
0, bestSol,
0, newBestSol.
length); bestCost = newBestCost;
}
}
}
if (city1 != 0) {
tabuList.decrementTabu();
tabuList.tabuMove(city1, city2);
}
return bestSol;
}
//swaps two cities
public static int[] swapOperator(int city1, int city2, int[] solution) {
int temp = solution[city1];
solution[city1] = solution[city2];
solution[city2] = temp;
return solution;
}
public static void main
(String[] args
) {
TSPEnvironment tspEnvironment = new TSPEnvironment();
tspEnvironment.distances = //Distance matrix, 5x5, used to represent distances
new int[][] {{0, 55, 75, 115, 170, 148, 145, 159, 98, 101, 95, 50, 58, 20},
{55, 0, 43, 115, 165, 155, 170, 198, 142, 138, 115, 75, 105, 75},
{75, 43, 0, 80, 125, 120, 142, 181, 135, 123, 91, 64, 106, 95},
{115 , 115, 80, 0, 56, 41, 72, 124, 102, 80, 40, 66, 100, 124},
{170, 165, 125, 56, 0, 41, 85, 145, 120, 88, 121, 151, 178 },
{148, 155, 120, 41, 41, 0, 45, 105, 105, 80, 55, 96, 120, 153},
{145, 170, 142, 72, 85, 45, 0, 60, 75, 53, 53, 98, 103, 145},
{159, 198, 181, 124, 145, 105, 60, 0, 63, 60, 91, 123, 104, 150},
{98, 142, 135, 102, 120, 105, 75, 63, 0, 25, 60,72, 40, 88},
{101, 138, 123, 80, 120, 80, 53, 60, 25, 0, 40, 65, 50, 95},
{95, 115, 91, 40, 88, 55, 53, 91, 60, 40, 0, 45, 64, 98},
{50, 75, 64, 66, 121, 96, 98, 123, 72, 65, 45, 0, 46, 58},
{58, 105, 106, 100, 151, 120, 103, 104, 40, 50, 64, 46, 0, 47},
{20, 75, 95, 124, 178, 153, 145, 150, 88, 95, 98, 58, 47, 0},
//Between cities. 0,1 represents distance between cities 0 and 1, and so on.
int[] currSolution = new int[]{0, 3,1, 4,2, 7,6,8,5,10,14,11,13,12,0}; //initial solution
//city numbers start from 0
// the first and last cities' positions do not change
int numberOfIterations = 1;
int tabuLength = 10;
TabuList tabuList = new TabuList(tabuLength);
int[] bestSol = new int[currSolution.length]; //this is the best Solution So Far
System.
arraycopy(currSolution,
0, bestSol,
0, bestSol.
length); int bestCost = tspEnvironment.getObjectiveFunctionValue(bestSol);
for (int i = 0; i < numberOfIterations; i++) { // perform iterations here
currSolution = TabuSearch.getBestNeighbour(tabuList, tspEnvironment, currSolution);
//printSolution(currSolution);
int currCost = tspEnvironment.getObjectiveFunctionValue(currSolution);
//System.out.println("Current best cost = " + tspEnvironment.getObjectiveFunctionValue(currSolution));
if (currCost < bestCost) {
System.
arraycopy(currSolution,
0, bestSol,
0, bestSol.
length); bestCost = currCost;
}
}
System.
out.
println("Search done! \nBest Solution cost found = " + bestCost
+ "\nBest Solution :");
printSolution(bestSol);
}
public static void printSolution(int[] solution) {
for (int i = 0; i < solution.length; i++) {
System.
out.
print(solution
[i
] + " "); }
}
}
/**
*
* @author http://v...content-available-to-author-only...y.com
* Use this code at your own risk ;)
*/
class TSPEnvironment { //Tabu Search Environment
public int [][] distances;
public TSPEnvironment(){
}
public int getObjectiveFunctionValue(int solution[]){ //returns the path cost
//the first and the last cities'
// positions do not change.
// example solution : {0, 1, 3, 4, 2, 0}
int cost = 0;
for(int i = 0 ; i < solution.length-1; i++){
cost+= distances[solution[i]][solution[i+1]];
}
return cost;
}
}
class TabuList {
int [][] tabuList ;
public TabuList(int numCities){
tabuList = new int[numCities][numCities]; //city 0 is not used here, but left for simplicity
}
public void tabuMove(int city1, int city2){ //tabus the swap operation
tabuList[city1][city2]+= 5;
tabuList[city2][city1]+= 5;
}
public void decrementTabu(){
for(int i = 0; i<tabuList.length; i++){
for(int j = 0; j<tabuList.length; j++){
tabuList[i][j]-=tabuList[i][j]<=0?0:1;
}
}
}
}
Ci8qKgogKgogKiBAYXV0aG9yIGh0dHA6Ly92Li4uY29udGVudC1hdmFpbGFibGUtdG8tYXV0aG9yLW9ubHkuLi55LmNvbQogKiBVc2UgdGhpcyBjb2RlIGF0IHlvdXIgb3duIHJpc2sgOykKICovCiBjbGFzcyBUYWJ1U2VhcmNoIHsKCiAgICBwdWJsaWMgc3RhdGljIGludFtdIGdldEJlc3ROZWlnaGJvdXIoVGFidUxpc3QgdGFidUxpc3QsCiAgICAgICAgICAgIFRTUEVudmlyb25tZW50IHRzcEVudmlyb21uZXQsCiAgICAgICAgICAgIGludFtdIGluaXRTb2x1dGlvbikgewoKCiAgICAgICAgaW50W10gYmVzdFNvbCA9IG5ldyBpbnRbaW5pdFNvbHV0aW9uLmxlbmd0aF07IC8vdGhpcyBpcyB0aGUgYmVzdCBTb2x1dGlvbiBTbyBGYXIKICAgICAgICBTeXN0ZW0uYXJyYXljb3B5KGluaXRTb2x1dGlvbiwgMCwgYmVzdFNvbCwgMCwgYmVzdFNvbC5sZW5ndGgpOwogICAgICAgIGludCBiZXN0Q29zdCA9IHRzcEVudmlyb21uZXQuZ2V0T2JqZWN0aXZlRnVuY3Rpb25WYWx1ZShpbml0U29sdXRpb24pOwogICAgICAgIGludCBjaXR5MSA9IDA7CiAgICAgICAgaW50IGNpdHkyID0gMDsKICAgICAgICBib29sZWFuIGZpcnN0TmVpZ2hib3IgPSB0cnVlOwoKICAgICAgICBmb3IgKGludCBpID0gMTsgaSA8IGJlc3RTb2wubGVuZ3RoIC0gMTsgaSsrKSB7CiAgICAgICAgICAgIGZvciAoaW50IGogPSAyOyBqIDwgYmVzdFNvbC5sZW5ndGggLSAxOyBqKyspIHsKICAgICAgICAgICAgICAgIGlmIChpID09IGopIHsKICAgICAgICAgICAgICAgICAgICBjb250aW51ZTsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICBpbnRbXSBuZXdCZXN0U29sID0gbmV3IGludFtiZXN0U29sLmxlbmd0aF07IC8vdGhpcyBpcyB0aGUgYmVzdCBTb2x1dGlvbiBTbyBGYXIKICAgICAgICAgICAgICAgIFN5c3RlbS5hcnJheWNvcHkoYmVzdFNvbCwgMCwgbmV3QmVzdFNvbCwgMCwgbmV3QmVzdFNvbC5sZW5ndGgpOwoJCQkJIFN5c3RlbS5vdXQucHJpbnQoImluaXQgIik7CiAgICAgICAgICAgICAgICBuZXdCZXN0U29sID0gc3dhcE9wZXJhdG9yKGksIGosIGluaXRTb2x1dGlvbik7IC8vVHJ5IHN3YXBwaW5nIGNpdGllcyBpIGFuZCBqCiAgICAgICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50KCJuZXcgIik7CiAgICAgICAgICAgICAgICBwcmludFNvbHV0aW9uKG5ld0Jlc3RTb2wpOwogICAgICAgICAgICAgICAgLy8gLCBtYXliZSB3ZSBnZXQgYSBiZXR0ZXJzb2x1dGlvbgogICAgICAgICAgICAgICAgaW50IG5ld0Jlc3RDb3N0ID0gdHNwRW52aXJvbW5ldC5nZXRPYmplY3RpdmVGdW5jdGlvblZhbHVlKG5ld0Jlc3RTb2wpOwoKCgogICAgICAgICAgICAgICAgaWYgKChuZXdCZXN0Q29zdCA+IGJlc3RDb3N0IHx8IGZpcnN0TmVpZ2hib3IpICYmIHRhYnVMaXN0LnRhYnVMaXN0W2ldW2pdID09IDApIHsgLy9pZiBiZXR0ZXIgbW92ZSBmb3VuZCwgc3RvcmUgaXQKICAgICAgICAgICAgICAgICAgICBmaXJzdE5laWdoYm9yID0gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgY2l0eTEgPSBpOwogICAgICAgICAgICAgICAgICAgIGNpdHkyID0gajsKICAgICAgICAgICAgICAgICAgICBTeXN0ZW0uYXJyYXljb3B5KG5ld0Jlc3RTb2wsIDAsIGJlc3RTb2wsIDAsIG5ld0Jlc3RTb2wubGVuZ3RoKTsKICAgICAgICAgICAgICAgICAgICBiZXN0Q29zdCA9IG5ld0Jlc3RDb3N0OwogICAgICAgICAgICAgICAgfQoKCiAgICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIGlmIChjaXR5MSAhPSAwKSB7CiAgICAgICAgICAgIHRhYnVMaXN0LmRlY3JlbWVudFRhYnUoKTsKICAgICAgICAgICAgdGFidUxpc3QudGFidU1vdmUoY2l0eTEsIGNpdHkyKTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIGJlc3RTb2w7CgoKICAgIH0KCiAgICAvL3N3YXBzIHR3byBjaXRpZXMKICAgIHB1YmxpYyBzdGF0aWMgaW50W10gc3dhcE9wZXJhdG9yKGludCBjaXR5MSwgaW50IGNpdHkyLCBpbnRbXSBzb2x1dGlvbikgewogICAgICAgIGludCB0ZW1wID0gc29sdXRpb25bY2l0eTFdOwogICAgICAgIHNvbHV0aW9uW2NpdHkxXSA9IHNvbHV0aW9uW2NpdHkyXTsKICAgICAgICBzb2x1dGlvbltjaXR5Ml0gPSB0ZW1wOwogICAgICAgIHJldHVybiBzb2x1dGlvbjsKICAgIH0KCiAgICBwdWJsaWMgc3RhdGljIHZvaWQgbWFpbihTdHJpbmdbXSBhcmdzKSB7CgogICAgICAgIFRTUEVudmlyb25tZW50IHRzcEVudmlyb25tZW50ID0gbmV3IFRTUEVudmlyb25tZW50KCk7CgogICAgICAgIHRzcEVudmlyb25tZW50LmRpc3RhbmNlcyA9IC8vRGlzdGFuY2UgbWF0cml4LCA1eDUsIHVzZWQgdG8gcmVwcmVzZW50IGRpc3RhbmNlcwogICAgICAgICAgICAgICAgbmV3IGludFtdW10gIHt7MCwgNTUsIDc1LCAxMTUsIDE3MCwgMTQ4LCAxNDUsIDE1OSwgOTgsIDEwMSwgOTUsIDUwLCA1OCwgMjB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgezU1LCAwLCA0MywgMTE1LCAxNjUsIDE1NSwgMTcwLCAxOTgsIDE0MiwgMTM4LCAxMTUsIDc1LCAxMDUsIDc1fSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHs3NSwgNDMsIDAsIDgwLCAxMjUsIDEyMCwgMTQyLCAxODEsIDEzNSwgMTIzLCA5MSwgNjQsIDEwNiwgOTV9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgezExNSAsIDExNSwgODAsIDAsIDU2LCA0MSwgNzIsIDEyNCwgMTAyLCA4MCwgNDAsIDY2LCAxMDAsIDEyNH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7MTcwLCAxNjUsIDEyNSwgNTYsIDAsIDQxLCA4NSwgMTQ1LCAxMjAsIDg4LCAxMjEsIDE1MSwgMTc4IH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7MTQ4LCAxNTUsIDEyMCwgNDEsIDQxLCAwLCA0NSwgMTA1LCAxMDUsIDgwLCA1NSwgOTYsIDEyMCwgMTUzfSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7MTQ1LCAxNzAsIDE0MiwgNzIsIDg1LCA0NSwgMCwgNjAsIDc1LCA1MywgNTMsIDk4LCAxMDMsIDE0NX0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7MTU5LCAxOTgsIDE4MSwgMTI0LCAxNDUsIDEwNSwgNjAsIDAsIDYzLCA2MCwgOTEsIDEyMywgMTA0LCAxNTB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgezk4LCAxNDIsIDEzNSwgMTAyLCAxMjAsIDEwNSwgNzUsIDYzLCAwLCAyNSwgNjAsNzIsIDQwLCA4OH0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgezEwMSwgMTM4LCAxMjMsIDgwLCAxMjAsIDgwLCA1MywgNjAsIDI1LCAwLCA0MCwgNjUsIDUwLCA5NX0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7OTUsIDExNSwgOTEsIDQwLCA4OCwgNTUsIDUzLCA5MSwgNjAsIDQwLCAwLCA0NSwgNjQsIDk4fSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHs1MCwgNzUsIDY0LCA2NiwgMTIxLCA5NiwgOTgsIDEyMywgNzIsIDY1LCA0NSwgMCwgNDYsIDU4fSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHs1OCwgMTA1LCAxMDYsIDEwMCwgMTUxLCAxMjAsIDEwMywgMTA0LCA0MCwgNTAsIDY0LCA0NiwgMCwgNDd9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgezIwLCA3NSwgOTUsIDEyNCwgMTc4LCAxNTMsIDE0NSwgMTUwLCA4OCwgOTUsIDk4LCA1OCwgNDcsIDB9LAogICAgICAgIC8vQmV0d2VlbiBjaXRpZXMuIDAsMSByZXByZXNlbnRzIGRpc3RhbmNlIGJldHdlZW4gY2l0aWVzIDAgYW5kIDEsIGFuZCBzbyBvbi4KCiAgICAgICAgaW50W10gY3VyclNvbHV0aW9uID0gbmV3IGludFtdezAsIDMsMSwgNCwyLCA3LDYsOCw1LDEwLDE0LDExLDEzLDEyLDB9OyAgLy9pbml0aWFsIHNvbHV0aW9uCiAgICAgICAgLy9jaXR5IG51bWJlcnMgc3RhcnQgZnJvbSAwCiAgICAgICAgLy8gdGhlIGZpcnN0IGFuZCBsYXN0IGNpdGllcycgcG9zaXRpb25zIGRvIG5vdCBjaGFuZ2UKCiAgICAgICAgaW50IG51bWJlck9mSXRlcmF0aW9ucyA9IDE7CiAgICAgICAgaW50IHRhYnVMZW5ndGggPSAxMDsKICAgICAgICBUYWJ1TGlzdCB0YWJ1TGlzdCA9IG5ldyBUYWJ1TGlzdCh0YWJ1TGVuZ3RoKTsKCiAgICAgICAgaW50W10gYmVzdFNvbCA9IG5ldyBpbnRbY3VyclNvbHV0aW9uLmxlbmd0aF07IC8vdGhpcyBpcyB0aGUgYmVzdCBTb2x1dGlvbiBTbyBGYXIKICAgICAgICBTeXN0ZW0uYXJyYXljb3B5KGN1cnJTb2x1dGlvbiwgMCwgYmVzdFNvbCwgMCwgYmVzdFNvbC5sZW5ndGgpOwogICAgICAgIGludCBiZXN0Q29zdCA9IHRzcEVudmlyb25tZW50LmdldE9iamVjdGl2ZUZ1bmN0aW9uVmFsdWUoYmVzdFNvbCk7CgogICAgICAgIGZvciAoaW50IGkgPSAwOyBpIDwgbnVtYmVyT2ZJdGVyYXRpb25zOyBpKyspIHsgLy8gcGVyZm9ybSBpdGVyYXRpb25zIGhlcmUKCiAgICAgICAgICAgIGN1cnJTb2x1dGlvbiA9IFRhYnVTZWFyY2guZ2V0QmVzdE5laWdoYm91cih0YWJ1TGlzdCwgdHNwRW52aXJvbm1lbnQsIGN1cnJTb2x1dGlvbik7CiAgICAgICAgICAgIC8vcHJpbnRTb2x1dGlvbihjdXJyU29sdXRpb24pOwogICAgICAgICAgICBpbnQgY3VyckNvc3QgPSB0c3BFbnZpcm9ubWVudC5nZXRPYmplY3RpdmVGdW5jdGlvblZhbHVlKGN1cnJTb2x1dGlvbik7CgogICAgICAgICAgICAvL1N5c3RlbS5vdXQucHJpbnRsbigiQ3VycmVudCBiZXN0IGNvc3QgPSAiICsgdHNwRW52aXJvbm1lbnQuZ2V0T2JqZWN0aXZlRnVuY3Rpb25WYWx1ZShjdXJyU29sdXRpb24pKTsKCiAgICAgICAgICAgIGlmIChjdXJyQ29zdCA8IGJlc3RDb3N0KSB7CiAgICAgICAgICAgICAgICBTeXN0ZW0uYXJyYXljb3B5KGN1cnJTb2x1dGlvbiwgMCwgYmVzdFNvbCwgMCwgYmVzdFNvbC5sZW5ndGgpOwogICAgICAgICAgICAgICAgYmVzdENvc3QgPSBjdXJyQ29zdDsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCJTZWFyY2ggZG9uZSEgXG5CZXN0IFNvbHV0aW9uIGNvc3QgZm91bmQgPSAiICsgYmVzdENvc3QgKyAiXG5CZXN0IFNvbHV0aW9uIDoiKTsKCiAgICAgICAgcHJpbnRTb2x1dGlvbihiZXN0U29sKTsKCgoKICAgIH0KCiAgICBwdWJsaWMgc3RhdGljIHZvaWQgcHJpbnRTb2x1dGlvbihpbnRbXSBzb2x1dGlvbikgewogICAgICAgIGZvciAoaW50IGkgPSAwOyBpIDwgc29sdXRpb24ubGVuZ3RoOyBpKyspIHsKICAgICAgICAgICAgU3lzdGVtLm91dC5wcmludChzb2x1dGlvbltpXSArICIgIik7CiAgICAgICAgfQogICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigpOwogICAgfQp9CgovKioKICoKICogQGF1dGhvciBodHRwOi8vdi4uLmNvbnRlbnQtYXZhaWxhYmxlLXRvLWF1dGhvci1vbmx5Li4ueS5jb20KICogVXNlIHRoaXMgY29kZSBhdCB5b3VyIG93biByaXNrIDspCiAqLwogY2xhc3MgVFNQRW52aXJvbm1lbnQgeyAvL1RhYnUgU2VhcmNoIEVudmlyb25tZW50CiAgICAKICAgIHB1YmxpYyBpbnQgW11bXSBkaXN0YW5jZXM7CiAgICAKICAgIHB1YmxpYyBUU1BFbnZpcm9ubWVudCgpewogICAgICAgIAogICAgfQogICAgCiAgICBwdWJsaWMgaW50IGdldE9iamVjdGl2ZUZ1bmN0aW9uVmFsdWUoaW50IHNvbHV0aW9uW10peyAvL3JldHVybnMgdGhlIHBhdGggY29zdAogICAgICAgIC8vdGhlIGZpcnN0IGFuZCB0aGUgbGFzdCBjaXRpZXMnCiAgICAgICAgLy8gIHBvc2l0aW9ucyBkbyBub3QgY2hhbmdlLgogICAgICAgIC8vIGV4YW1wbGUgc29sdXRpb24gOiB7MCwgMSwgMywgNCwgMiwgMH0KICAgICAgCiAgICAgICAgaW50IGNvc3QgPSAwOwogICAKICAgICAgICBmb3IoaW50IGkgPSAwIDsgaSA8IHNvbHV0aW9uLmxlbmd0aC0xOyBpKyspewogICAgICAgICAgICBjb3N0Kz0gZGlzdGFuY2VzW3NvbHV0aW9uW2ldXVtzb2x1dGlvbltpKzFdXTsKICAgICAgICB9CiAgIAogICAgICAgIHJldHVybiBjb3N0OwogICAgICAgIAogICAgfQogICAgCiAgIAoKfQoKY2xhc3MgVGFidUxpc3QgewogICAgCiAgICBpbnQgW11bXSB0YWJ1TGlzdCA7CiAgICBwdWJsaWMgVGFidUxpc3QoaW50IG51bUNpdGllcyl7CiAgICAgICAgdGFidUxpc3QgPSBuZXcgaW50W251bUNpdGllc11bbnVtQ2l0aWVzXTsgLy9jaXR5IDAgaXMgbm90IHVzZWQgaGVyZSwgYnV0IGxlZnQgZm9yIHNpbXBsaWNpdHkKICAgIH0KICAgIAogICAgcHVibGljIHZvaWQgdGFidU1vdmUoaW50IGNpdHkxLCBpbnQgY2l0eTIpeyAvL3RhYnVzIHRoZSBzd2FwIG9wZXJhdGlvbgogICAgICAgIHRhYnVMaXN0W2NpdHkxXVtjaXR5Ml0rPSA1OwogICAgICAgIHRhYnVMaXN0W2NpdHkyXVtjaXR5MV0rPSA1OwogICAgICAgIAogICAgfQogICAgCiAgICBwdWJsaWMgdm9pZCBkZWNyZW1lbnRUYWJ1KCl7CiAgICAgICAgZm9yKGludCBpID0gMDsgaTx0YWJ1TGlzdC5sZW5ndGg7IGkrKyl7CiAgICAgICAgICAgZm9yKGludCBqID0gMDsgajx0YWJ1TGlzdC5sZW5ndGg7IGorKyl7CiAgICAgICAgICAgIHRhYnVMaXN0W2ldW2pdLT10YWJ1TGlzdFtpXVtqXTw9MD8wOjE7CiAgICAgICAgIH0gCiAgICAgICAgfQogICAgfQogICAgCn0KCg==