/** * * --Copyright notice-- * * Author: Andy Evans, based on code by Dimitris Ballas. * * This code is also licensed under GPL2 which can be found at * the Open Source Initiative website at... * http://cran.r-project.org/web/licenses/GPL-2 * * --End of Copyright notice-- * **/ // Simulated Annealling code from simple Microsimulation App. You can find the app online at: // http://www.geog.leeds.ac.uk/groups/mass/internal/software /** * The meat of this particular program. Takes each area in turn, and swaps people in and out until statistics ok.
* The code works broadly by swapping random people out of the area, and replacing them by * another random person. The new error between the current statistics and those we're hoping * for is assessed, and if there's an improvement the change is kept, otherwise the old person is * put back in and the new one removed. This "gradient descent" style method is adjusted by the * Simulated Annealing algorithm, which allows worse errors to be kept with a probablity that * reduces over time. As this is a toy application with only one * attribute of two values, the SA routine actually slows down the basic gradient * descent algorithm, but if there were multiple attributes that needed fitting, it would * be a real boon. **/ private void redistribute() { for (int area = 0; area < numberOfAreas; area++) { messageBox.append("\n\nDoing area " + area + "\n"); // Set up the SA temperature to drop. double temperature = 0; int minError = -1; Vector minErrorPeople = null; int areaError = 0; for (int i = maxTemperature; i > 0; i--) { // The next line is lifted almost entirely from Dimitris' SimLeeds. temperature = (double)temperatureConversion*((double)i/(double)maxTemperature); // Calculate the current error for the area. We're going // to carry on until the error is low, or we exceed a fixed // number of runs. In addition, each time a minimum error set // of people is generated, we record this, as the algorithm // can wander off into bad solutions and get lost. areaError = calculateError(area); if (minError == -1) { minError = areaError; minErrorPeople = (Vector)(world[area].clone()); } int runs = 0; // Start swapping. while ((areaError > errorMargin) && (runs < maxRuns)) { // Replace one of the people in the area with someone new. int person = (int)(world[area].size() * Math.random()); Person oldPerson = (Person) world[area].elementAt(person); world[area].remove(oldPerson); Person newPerson = getRandomPerson(); world[area].addElement(newPerson); // Recalculate the error and decide whether to keep them or not. int newAreaError = calculateError(area); if (newAreaError > areaError) { // Keep bad choices with a probablity relating to how bad they are and // the current temperature. // The next line is lifted almost entirely from Dimitris' SimLeeds. if (Math.random() < Math.exp((-1 * ((double)newAreaError - (double)areaError))/temperature)) { areaError = newAreaError; } else { world[area].remove(newPerson); world[area].addElement(oldPerson); } } else { areaError = newAreaError; // If this is the lowest error we've seen, record the people. if (areaError < minError) { minError = areaError; minErrorPeople = (Vector)(world[area].clone()); } } messageBox.append(tableToReplicate.rowToString(area) + " Target " + currentTable.rowToString(area) + " error = " + areaError + "\n"); runs++; } // End of swapping while loop. // If we're ok with the current answer, don't bother reducing the temperature. if (areaError < errorMargin) break; } // End of temperature decrease for loop. // Check to make sure the error is the lowest we've seen, and if it isn't, replace the people. if (areaError > minError) { world[area] = minErrorPeople; } } // End of doing all the areas. messageBox.append("\n\nDone:\n"); for (int area = 0; area < numberOfAreas; area++) { messageBox.append(tableToReplicate.rowToString(area) + " Target " + currentTable.rowToString(area) + "\n"); } } // End of redistribute.