package org.societies.context.user.refinement.impl.bayesianLibrary.bayesianLearner.impl; import java.io.FileWriter; import java.io.IOException; import java.util.Date; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.societies.context.user.refinement.impl.bayesianLibrary.bayesianLearner.interfaces.Candidate; import org.societies.context.user.refinement.impl.bayesianLibrary.bayesianLearner.interfaces.CandidatesGenerator; import org.societies.context.user.refinement.impl.bayesianLibrary.bayesianLearner.interfaces.SearchConsumer; import org.societies.context.user.refinement.impl.bayesianLibrary.bayesianLearner.interfaces.Searcher; import org.societies.context.user.refinement.impl.tools.LearningPropertyLoader; /** * This is the implementation of a Hill Climbing search algorithm. The main method * is the private method processNextGeneration() that is called from the endless loop * in the run() method. * @author robert_p * */ public class BasicGreedyHillClimber implements Searcher{ private static double RestartConfiguration_PercentageOfGlobalRandomRestarts = 90.0; private static Logger log4j = LoggerFactory.getLogger(BasicGreedyHillClimber.class); Logger restartModeLog4j = LoggerFactory.getLogger("modeSwitching"); private Thread thread; private boolean toStop = true; private Candidate bestCandidate; private Candidate bestLocalCandidate; private Candidate tempBestCandidate; private SearchConsumer searchConsumer; private CandidatesGenerator candidates; private Set<Candidate> databaseOfBest; private Set<Candidate> databaseOfRandomRestartedCandidates; private int counter; private int genCounter; private long randomRestartCounter; private long numberOfcandidatesSearchedSinceLastBestFound; private long randomRestartAttemptedCounter; private double averageCacheHitRate; private int restartsAttemptedSinceLastBestFound; private long timeOfLastSuccessOrTravelStart; private int millisForTravellingRestartModePeriod; private int millisForLocalRestartModePeriod; private int currentRestartState = 1; private int localBestRestarts; public static final int RestartState_SearchSpaceAroundTempBest = 1; public static final int RestartState_TravelAway = 2; public static final int RestartState_StopTravelling = 3; private static final boolean AllowDuplicates = true; private static boolean IAmProcessingADuplicate = false; private static int maxLocalRestarts = 0; /** * @param candidatesGenerator * @param bestCandidateMemory * @param startingCandidate * @param searchConsumer */ public BasicGreedyHillClimber(CandidatesGenerator candidatesGenerator, SearchConsumer searchConsumer) { this(); this.initialise(candidatesGenerator, searchConsumer); } public BasicGreedyHillClimber() { RestartConfiguration_PercentageOfGlobalRandomRestarts = LearningPropertyLoader.getRestartConfiguration_PercentageOfGlobalRandomRestarts(); millisForLocalRestartModePeriod = (int) Math.round(LearningPropertyLoader.getRestartConfiguration_DurationOfRandomRestartsWithAbsoluteBestInHours()*60*60*1000); millisForTravellingRestartModePeriod = (int) Math.round(LearningPropertyLoader.getRestartConfiguration_DurationOfRandomRestartsWithLocalBestInHours()*60*60*1000); maxLocalRestarts = LearningPropertyLoader.getRestartConfiguration_maxLocalRestarts(); this.databaseOfBest = new HashSet<Candidate>(); this.databaseOfRandomRestartedCandidates = new HashSet<Candidate>(); } public BasicGreedyHillClimber(CandidatesGenerator candidatesGenerator, Candidate startingCandidate, SearchConsumer searchConsumer) { this(); this.initialise(candidatesGenerator, startingCandidate, searchConsumer); } public void initialise(CandidatesGenerator candidatesGenerator, Candidate startingCandidate, SearchConsumer searchConsumer) { synchronized (this) { if (this.toStop==false) { this.stopSearch(); } this.candidates = candidatesGenerator; this.databaseOfRandomRestartedCandidates.clear(); this.bestCandidate = candidatesGenerator.makeEmptyCandidate(); this.tempBestCandidate = candidatesGenerator.makeEmptyCandidate(); this.bestCandidate.importFrom(startingCandidate); this.bestLocalCandidate = candidatesGenerator.makeEmptyCandidate(); this.bestLocalCandidate.importFrom(startingCandidate); this.searchConsumer = searchConsumer; this.numberOfcandidatesSearchedSinceLastBestFound = 0; this.randomRestartAttemptedCounter = 0; this.randomRestartCounter = 0; this.timeOfLastSuccessOrTravelStart = System.currentTimeMillis(); this.candidates.initialise(startingCandidate); this.databaseOfBest.clear(); } } public void initialise(CandidatesGenerator candidatesGenerator, SearchConsumer searchConsumer) { Candidate startingCandidate = candidatesGenerator.makeStartingCandidate(); this.initialise(candidatesGenerator, startingCandidate, searchConsumer); } public synchronized void startSearch() { this.toStop = false; this.thread = new Thread(this); this.thread.setName("BasicGreedyHillClimber Thread"); this.thread.start(); } public synchronized void stopSearch() { this.toStop = true; this.thread.interrupt(); log4j.info(randomRestartCounter+" random restarts executed. Duplicates:" + (this.randomRestartAttemptedCounter-this.randomRestartCounter)); } public Candidate returnBestCandidateSoFar() { synchronized (this) { return this.bestCandidate; } } public long numberOfcandidatesSearchedSinceLastBestFound() { synchronized (this) { return this.numberOfcandidatesSearchedSinceLastBestFound; } } public double computeFitnessRatioToBestCandidateSoFar(Candidate otherCandidate) { synchronized (this) { return this.bestCandidate.computeFitness() / otherCandidate.computeFitness(); } } public void run() { try { this.counter=0; while (!this.toStop) { try { this.processNextGeneration(); this.counter++; } catch (InterruptedException e) { System.err.println(this.thread.getName() + " was interrupted. To-stop is: " + this.toStop); } } log4j.debug("Really Stopped at: "+System.currentTimeMillis()); System.err.println(" Stopped " + this.thread.getName()); } catch (Throwable t) { t.printStackTrace(); } } /** * This method is called from the endless loop in run(). It takes the this.bestLocalCandidate * and initializes the this.candidates pool from it, that is then used for the current generation. * this.candidates.returnEnumerationOverModifiedCandidates() is then used to go over all * possible candidates which are evaluated in terms of their fitness. * @throws InterruptedException */ private void processNextGeneration() throws InterruptedException{ // System.out.println(this.thread.getName() + " processing "+ this.genCounter + " generation. Total analysed " + counter); boolean stuckInLocalMinimum = true; this.genCounter++; this.candidates.initialise(this.bestLocalCandidate); restartModeLog4j.debug(""+this.bestLocalCandidate); Enumeration<Candidate> enumer = this.candidates.returnEnumerationOverModifiedCandidates(); while (enumer.hasMoreElements()) { this.counter++; Candidate newCandidate = (Candidate) enumer.nextElement(); if (newCandidate==null) continue; double fitness = newCandidate.computeFitness(); this.numberOfcandidatesSearchedSinceLastBestFound++; if (newCandidate.isValid()) { double epsilon = 0.5; if (this.counter==1 || this.bestCandidate.computeFitness() < fitness + epsilon || ((this.bestCandidate.computeFitness() == fitness) && newCandidate.getSecondaryFitness() > this.bestCandidate.getSecondaryFitness())) { boolean foundSignificantBetter = (this.bestCandidate.computeFitness() < fitness - epsilon || this.counter==1); boolean foundAbsoluteBest = (this.bestCandidate.computeFitness() < fitness) || this.counter==1; if (!this.databaseOfBest.contains(newCandidate)) { Candidate storedCandidate = newCandidate.cloneCandidate(); this.databaseOfBest.add(storedCandidate); synchronized (this) { double oldBestscore = this.bestCandidate.computeFitness(); if (foundAbsoluteBest) { this.averageCacheHitRate = 0.0; this.bestCandidate.importFrom(newCandidate); this.tempBestCandidate.importFrom(bestCandidate); this.numberOfcandidatesSearchedSinceLastBestFound = 0; this.restartsAttemptedSinceLastBestFound = 0; this.timeOfLastSuccessOrTravelStart = System.currentTimeMillis(); this.localBestRestarts =0; this.currentRestartState = RestartState_SearchSpaceAroundTempBest; if (restartModeLog4j.isDebugEnabled()) restartModeLog4j.debug("Switched Search mode to state="+currentRestartState+ " at "+new Date(System.currentTimeMillis())); if (IAmProcessingADuplicate){ restartModeLog4j.error("Found new absolute best while processing a duplicate candidate:\n"); restartModeLog4j.error(""+bestCandidate); restartModeLog4j.error("Fitness="+fitness); restartModeLog4j.error("\nExiting now!"); System.exit(9); } } this.searchConsumer.notifyNewSearchResult(newCandidate, oldBestscore, false, this.counter, this.genCounter, this.randomRestartCounter, foundAbsoluteBest, foundSignificantBetter); } } } // if we have improved the best local candidate in this generation, then we // are not stuck in a local minimum! if (this.bestLocalCandidate.computeFitness() < fitness) { stuckInLocalMinimum = false; this.bestLocalCandidate.importFrom(newCandidate); } } else { // System.out.println("new found candidate has arcs:"); // System.err.println("new found candidate has arcs:"); } // System.out.println("Current fitness: " + fitness + " Best Fitness so far: " + this.bestCandidate.computeFitness() + // " for: this candidate:\n" + this.bestCandidate + "\n best local for this: " + // this.bestLocalCandidate.computeFitness() + " for : " + this.bestLocalCandidate); // System.out.println(this.thread.getName() + " doing rollback"); // System.out.println(this.thread.getName() + " done rollback"); } if (log4j.isDebugEnabled()) log4j.debug(this.thread.getName()+ " best score: " + this.bestCandidate.computeFitness() + ". Local score: " + this.bestLocalCandidate.computeFitness() + " processed "+ this.genCounter + " generation. Total analysed: " + counter + " restarts: " + this.randomRestartCounter); if (log4j.isDebugEnabled()) if (this.genCounter % 10 == 0) log4j.debug(this.thread.getName() + " Time: " + new Date(System.currentTimeMillis()) + " Total Memory: " + Runtime.getRuntime().totalMemory()/1024 + " Free Memory: " + Runtime.getRuntime().freeMemory()/1024); // if we are stuck in a local minimum for thw whole generation then we do a random restart. if (stuckInLocalMinimum) { // System.exit(-1); this.randomRestartCounter++; //switch localRestart mode if (currentRestartState==RestartState_SearchSpaceAroundTempBest){ this.bestLocalCandidate.importFrom(this.tempBestCandidate); if ( System.currentTimeMillis() > (this.timeOfLastSuccessOrTravelStart + millisForLocalRestartModePeriod)) { currentRestartState = RestartState_TravelAway; timeOfLastSuccessOrTravelStart = System.currentTimeMillis(); if (restartModeLog4j.isDebugEnabled()){ restartModeLog4j.debug("Switched Search mode to state="+currentRestartState+ " with cache/hit="+averageCacheHitRate+" at "+new Date(System.currentTimeMillis())); } } } else if (currentRestartState==RestartState_StopTravelling){ this.bestLocalCandidate.importFrom(this.bestCandidate); timeOfLastSuccessOrTravelStart = System.currentTimeMillis(); this.averageCacheHitRate = 0.0; this.localBestRestarts =0; currentRestartState = RestartState_TravelAway; if (restartModeLog4j.isDebugEnabled()) restartModeLog4j.debug("Switched Search mode to state="+currentRestartState+ " with cache/hit="+averageCacheHitRate+ " at "+new Date(System.currentTimeMillis())); } else if (currentRestartState==RestartState_TravelAway){ if (maxLocalRestarts < localBestRestarts) { currentRestartState = RestartState_StopTravelling; if (restartModeLog4j.isDebugEnabled()) restartModeLog4j.debug("Switched Search mode to state="+currentRestartState+ " with cache/hit="+averageCacheHitRate+ " at "+new Date(System.currentTimeMillis())); } else if ( System.currentTimeMillis() > (this.timeOfLastSuccessOrTravelStart + millisForTravellingRestartModePeriod)) { //todo change currentRestartState = RestartState_SearchSpaceAroundTempBest; tempBestCandidate.importFrom(bestLocalCandidate); timeOfLastSuccessOrTravelStart = System.currentTimeMillis(); localBestRestarts++; if (restartModeLog4j.isDebugEnabled()) restartModeLog4j.debug("Switched Search mode to state="+currentRestartState+ " with cache/hit="+averageCacheHitRate+ " at "+new Date(System.currentTimeMillis())); } } // if (this.randomForRandomRestartStartingPoint.nextDouble() <= RestartConfiguration_PercentageOfGlobalRandomRestarts/100.0) { boolean duplicate = false; IAmProcessingADuplicate = false; //ignore already visited duplicates! do { this.bestLocalCandidate.randomRestart(restartsAttemptedSinceLastBestFound++, this.averageCacheHitRate); this.randomRestartAttemptedCounter++; duplicate = this.databaseOfRandomRestartedCandidates.contains(this.bestLocalCandidate); if (duplicate && AllowDuplicates) IAmProcessingADuplicate = true; // TODO ERROR SEARCHING: REMOVE: START if (duplicate){ boolean allDifferent = true; for (Candidate bnc : databaseOfRandomRestartedCandidates){ if (bestLocalCandidate.toString().equals(bnc.toString())){ restartModeLog4j.debug("\n\n\nDuplicate found in database\n"+bnc.toString()+" equals to:"); restartModeLog4j.debug(bestLocalCandidate.toString() + "\n\n\n"); allDifferent = false; break; } } if (allDifferent){ restartModeLog4j.error("Error: Duplicate=true, but all database entries are different:\n\n"+bestLocalCandidate); restartModeLog4j.error("\n\nDatabase:\n"); for (Candidate bnc : databaseOfRandomRestartedCandidates){ restartModeLog4j.error(""+bnc); } System.exit(1); } } // TODO ERROR SEARCHING: REMOVE: END //Leaky bucket filter to estimate the local cache:hit rate if (duplicate) { if (restartModeLog4j.isDebugEnabled()) restartModeLog4j.debug(" found Duplicate candidate after restart. numberOfcandidatesSearchedSinceLastBestFound: " + this.numberOfcandidatesSearchedSinceLastBestFound); this.averageCacheHitRate = 0.99 * this.averageCacheHitRate + 0.01; } else { this.averageCacheHitRate = 0.99 * this.averageCacheHitRate; } if (AllowDuplicates) duplicate=false; } while (duplicate); this.databaseOfRandomRestartedCandidates.add(this.bestLocalCandidate); restartModeLog4j.debug("Adding to DB:\n"+this.bestLocalCandidate+"\nDB size="+databaseOfRandomRestartedCandidates.size()+"\n"); if (log4j.isDebugEnabled()) log4j.debug("Doing the " + this.randomRestartCounter + " 'th random restart"); // System.exit(-1); } // System.exit(-1); } /** * @param bestCandidate * @param counter * @param genCounter * @param randomRestartsCounter */ public static void updateResultFiles(String filename, double oldBestscore, Candidate bestCandidate, int counter, long genCounter, long randomRestartsCounter, boolean append) { if (log4j.isDebugEnabled()) log4j.debug("Best candidate: " + bestCandidate + " fitness: " + bestCandidate.computeFitness()); FileWriter fos = null; try { fos = new FileWriter(filename, append); } catch (IOException e1) { e1.printStackTrace(); } try { //MODIFIED by Maria fos.write(new Date(System.currentTimeMillis()).toString()+" "); fos.write((genCounter + "/" + counter+"/" + randomRestartsCounter + " Best candidate: " + bestCandidate + "\n fitness: " + + bestCandidate.computeFitness() + " old best fitness: " + oldBestscore + "\n").toCharArray()); //END MODIFIED by Maria //fos.write((genCounter + "/" + counter+"/" + randomRestartsCounter + " Best candidate: " + bestCandidate + "\n fitness: " + // + bestCandidate.computeFitness() + " old best fitness: " + oldBestscore + "\n").toCharArray()); fos.close(); } catch (IOException e2) { e2.printStackTrace(); } } }