package javaeva.server.go.operators.postprocess; import java.util.Collection; import javaeva.OptimizerFactory; import javaeva.OptimizerRunnable; import javaeva.gui.BeanInspector; import javaeva.server.go.InterfaceTerminator; import javaeva.server.go.individuals.AbstractEAIndividual; import javaeva.server.go.operators.cluster.ClusteringDensityBased; import javaeva.server.go.operators.cluster.InterfaceClustering; import javaeva.server.go.operators.distancemetric.InterfaceDistanceMetric; import javaeva.server.go.operators.distancemetric.PhenotypeMetric; import javaeva.server.go.operators.mutation.InterfaceMutation; import javaeva.server.go.operators.mutation.MutateESFixedStepSize; import javaeva.server.go.operators.terminators.EvaluationTerminator; import javaeva.server.go.populations.Population; import javaeva.server.go.problems.AbstractOptimizationProblem; import javaeva.server.go.problems.FM0Problem; import javaeva.server.go.problems.InterfaceMultimodalProblemKnown; import javaeva.server.go.strategies.HillClimbing; import javaeva.server.stat.InterfaceTextListener; import javaeva.tools.Pair; /** * Postprocess a population / list of individuals to find out a set of distinct optima. * * @author mkron * */ public class PostProcess { protected static InterfaceDistanceMetric metric = new PhenotypeMetric(); private static final boolean TRACE = false; // the default mutation step size for HC post processing private static double defaultMutationStepSize = 0.01; // used for hill climbing post processing and only alive during that period private static OptimizerRunnable hcRunnable = null; public static final int BEST_ONLY = 1; public static final int BEST_RAND = 2; public static final int RAND_ONLY = 3; public static final int KEEP_LONERS = 11; public static final int DISCARD_LONERS = 12; public static final int LONERS_AS_CLUSTERS = 13; /** * This method returns a set of individuals corresponding to an optimum in a given list. * The individuals returned are to be nearer than epsilon to a given optimum. For each optimum, there is * returned zero or one individual at max. * If there are several individuals close to an optimum, the fitter one or the closer one * may be selected, indicated by the boolean flag bTakeFitter. This means, that an individual may be * returned in more than one copy if the optima are close together and the individual lies in between. * The returned array may contain null values if an optimum is not considered found at all. * * @param pop A population of possible solutions. * @param optima a set of predefined optima * @param epsilon the threshold up to which an optimum is considered found. * @param bTakeFitter if true, the fitter of two close individuals is selected, otherwise the closer one * @return an array of individuals corresponding to the optimum with the same index */ public static AbstractEAIndividual[] getFoundOptimaArray(Population pop, Population optima, double epsilon, boolean bTakeFitter) { AbstractEAIndividual candidate, opt; // Population result = new Population(5); AbstractEAIndividual[] found = new AbstractEAIndividual[optima.size()]; double indDist; for (int i = 0; i < found.length; i++) found[i] = null; for (int i = 0; i < pop.size(); i++) { candidate = (AbstractEAIndividual) pop.get(i); for (int j = 0; j < optima.size(); j++) { opt = (AbstractEAIndividual) optima.get(j); indDist = metric.distance(candidate, opt); if (found[j] == null) { // current optimum has not been found yet if (indDist < epsilon) { found[j] = (AbstractEAIndividual)candidate.clone(); // result.add(found[j]); } } else {// there was another one found. set the fitter one or the closer one if (indDist < epsilon) { if ((bTakeFitter && (candidate.isDominatingDebConstraints(found[j]))) // flag "fitter" and new one is fitter || (!bTakeFitter && (indDist < metric.distance(found[j], opt)))) { // flag "closer" and new one is closer // int index = result.indexOf(found[j]); // do replacement found[j] = (AbstractEAIndividual)candidate.clone(); // result.set(index, found[j]); } } } } } return found; } /** * Convenience method for getFoundOptimaArray(), returning the same set of optima in a Population. * * @see getFoundOptimaArray(Population pop, Population optima, double epsilon, boolean bTakeFitter) * @param pop A population of possible solutions. * @param optima a set of known optima * @param epsilon the threshold up to which an optimum is considered found. * @param bTakeFitter if true, the fitter of two close individuals is selected, otherwise the closer one * @return a Population of individuals corresponding to the given optima */ public static Population getFoundOptima(Population pop, Population optima, double epsilon, boolean bTakeFitter) { Population result = new Population(5); AbstractEAIndividual[] optsFound = getFoundOptimaArray(pop, optima, epsilon, bTakeFitter); for (int i=0; i 0, at least one is kept. * lonerMode defines whether loners are discarded, kept, or treated as clusters, meaning they are kept if returnQuota > 0. * takOverMode defines whether, of a cluster with size > 1, which n individuals are kept. Either the n best only, * or the single best and random n-1, or all n random. * * @param pop * @param clustering * @param returnQuota * @param lonerMode * @param takeOverMode * @return for every cluster a population of representatives which are the best individuals or a random subset of the cluster */ public static Population clusterBest(Population pop, InterfaceClustering clustering, double returnQuota, int lonerMode, int takeOverMode) { //cluster the undifferentiated population Population result = new Population(10); result.setSameParams(pop); Population[] clusters = clustering.cluster(pop); if (TRACE) { System.out.println("found " + clusters.length + " clusters!"); int sum=0; for (int j=0; j= 1) result.addAll((Collection)clusters[j]); // easy case else { int n = Math.max(1, (int)(returnQuota*clusters[j].size())); // return at least one per cluster! switch (takeOverMode) { case BEST_ONLY: // another easy case result.addAll((Collection)(clusters[j].getBestNIndividuals(n))); break; case BEST_RAND: Population exclude = new Population(); exclude.add(clusters[j].getBestEAIndividual()); result.add(exclude.getEAIndividual(0)); result.addAll(clusters[j].getRandNIndividualsExcept(n-1, exclude)); break; case RAND_ONLY: result.addAll(clusters[j].getRandNIndividuals(n)); break; default: System.err.println("Unknown mode in PostProcess:clusterBest!"); break; } } } result.setPopulationSize(result.size()); return result; } public static double[] populationMeasures(Population pop) { double[] measures = pop.getPopulationMeasures(); return measures; } /** * Filter the individuals of a population which have a fitness norm within given bounds. * Returns shallow copies! * * @param pop * @param lower * @param upper * @return */ public static Population filterFitnessIn(Population pop, double lower, double upper) { Population result = filterFitness(pop, upper, true); return filterFitness(result, lower, false); } /** * Filter the individuals of a population which have a fitness norm below a given value. * Returns shallow copies! * * @param pop * @param fitNorm * @param bSmaller if true, return individuals with lower or equal, else with higher fitness norm only * @return */ public static Population filterFitness(Population pop, double fitNorm, boolean bSmallerEq) { AbstractEAIndividual indy; Population result = new Population(); for (int i=0; ifitNorm)) result.add(indy); } return result; } /** * Create a fitness histogram of an evaluated population within the given interval and nBins number of bins. * Therefore a bin is of size (upperBound-lowerBound)/nBins, and bin 0 starts at lowerBound. * Returns an integer array with the number of individuals in each bin. * * @param pop the population to scan. * @param lowerBound lower bound of the fitness interval * @param upperBound upper bound of the fitness interval * @param nBins number of bins * @return an integer array with the number of individuals in each bin * @see filterFitnessIn() */ public static int[] createFitNormHistogram(Population pop, double lowerBound, double upperBound, int nBins) { int[] res = new int[nBins]; double lower = lowerBound; double step = (upperBound - lowerBound) / nBins; for (int i=0; i getClosestFoundOptima(Population pop, Population optima, double epsilon) { // AbstractEAIndividual indy; // ArrayList result = new ArrayList(5); // AbstractEAIndividual[] foundIndy = new AbstractEAIndividual[optima.size()]; // // for (int i = 0; i < pop.size(); i++) { // indy = (AbstractEAIndividual) pop.get(i); // IndexFitnessPair bestHit = getClosestIndy(indy, optima); // if (foundIndy[bestHit.index] == null) { // there has no indy been assigned yet // // assign the current indy if no epsilon is required, or epsilon-threshold is fulfilled // if (epsilon < 0 || (bestHit.dist < epsilon)) foundIndy[bestHit.index] = indy; // } else { // // assign current indy only if it is better than the earlier assigned one // // in that case, epsilon is fulfilled automatically // double oldDist = metric.distance(foundIndy[bestHit.index], (AbstractEAIndividual)optima.get(bestHit.index)); // if (bestHit.dist < oldDist) foundIndy[bestHit.index] = indy; // } // } // return result; // } /** * Search a population and find the closest individual to a given individual. Return the * best distance and corresponding index in a pair structure. * * @param indy * @param pop * @return index and distance to the closest individual in the population */ public static Pair getClosestIndy(AbstractEAIndividual indy, Population pop) { double bestDist = -1, tmpDist = -1; int bestIndex = -1; AbstractEAIndividual opt; for (int j = 0; j < pop.size(); j++) { opt = (AbstractEAIndividual) pop.get(j); tmpDist = metric.distance(indy, opt); // distance current indy to current optimum if (bestDist < 0 || (tmpDist < bestDist)) { // we have a better hit bestIndex = j; bestDist = tmpDist; } } return new Pair(bestDist, bestIndex); } /** * Optimize a population with a default hill-climbing heuristic for a number of fitness evaluations. * As mutation operator, a fixed step size ES mutation is used. * * @param pop * @param problem * @param maxSteps * @param fixedStepSize */ public static int processWithHC(Population pop, AbstractOptimizationProblem problem, int maxSteps, double fixedStepSize) { // pop.SetFunctionCalls(0); // or else optimization wont restart on an "old" population // pop.setGenerationTo(0); int stepsBef = pop.getFunctionCalls(); processWithHC(pop, problem, new EvaluationTerminator(pop.getFunctionCalls()+maxSteps), new MutateESFixedStepSize(fixedStepSize)); return pop.getFunctionCalls()-stepsBef; } public static int processWithHC(Population pop, AbstractOptimizationProblem problem, int maxSteps) { return processWithHC(pop, problem, maxSteps, defaultMutationStepSize); } /** * Optimize a population with a default hill-climbing heuristic with a given termination criterion and mutation operator. * * @param pop * @param problem * @param term * @param mute */ public static void processWithHC(Population pop, AbstractOptimizationProblem problem, InterfaceTerminator term, InterfaceMutation mute) { HillClimbing hc = new HillClimbing(); // HC depends heavily on the selected mutation operator! hc.SetProblem(problem); hc.SetMutationOperator(mute); if (pop.size() != pop.getPopulationSize()) { System.err.println(pop.size() + " vs. "+ pop.getPopulationSize()); System.err.println("warning: population size and vector size dont match! (PostProcess::processWithHC)"); } hc.setPopulation(pop); // hc.initByPopulation(pop, false); hcRunnable = new OptimizerRunnable(OptimizerFactory.makeParams(hc, pop, problem, 0, term), null, true); hcRunnable.getGOParams().setDoPostProcessing(false); hcRunnable.run(); hcRunnable.getGOParams().setDoPostProcessing(true); hcRunnable = null; } /** * Stop the hill-climbing post processing if its currently running. */ public static void stopHC() { if (hcRunnable != null) synchronized (hcRunnable) { if (hcRunnable != null) hcRunnable.stopOpt(); } } public static void main(String[] args) { AbstractOptimizationProblem problem = new FM0Problem(); InterfaceMultimodalProblemKnown mmp = (InterfaceMultimodalProblemKnown)problem; OptimizerRunnable runnable = OptimizerFactory.getOptRunnable(OptimizerFactory.STD_GA, problem, 500, null); runnable.run(); Population pop = runnable.getGOParams().getOptimizer().getPopulation(); // System.out.println("no optima found: " + mmp.getNumberOfFoundOptima(pop)); Population found = getFoundOptima(pop, mmp.getRealOptima(), 0.05, true); System.out.println("all found (" + found.size() + "): " + BeanInspector.toString(found)); Pair popD = new Pair(pop, 1.); int i=0; int evalCnt = 0; while (popD.tail() > 0.01) { i++; // public static PopDoublePair clusterHC(pop, problem, sigmaCluster, funCalls, keepClusterRatio, mute) { popD = clusterHC(popD.head(), problem, 0.01, 1000 - (evalCnt % 1000), 0.5, new MutateESFixedStepSize(0.02)); evalCnt += popD.head().getFunctionCalls(); } found = getFoundOptima(popD.head(), mmp.getRealOptima(), 0.05, true); System.out.println("found at " + i + " (" + found.size() + "): " + BeanInspector.toString(found)); System.out.println("funcalls: " + evalCnt); // System.out.println(BeanInspector.toString(pop.getMeanFitness())); // System.out.println("no optima found: " + mmp.getNumberOfFoundOptima(pop)); // System.out.println("best after: " + AbstractEAIndividual.getDefaultStringRepresentation(pop.getBestEAIndividual())); } /** * Cluster a population and reduce it by a certain ratio, then optimize the remaining individuals for a given number of function calls with a HC. * Return a pair of the optimized population and the improvement in the mean fitness (not normed) that was achieved by the HC run. The returned * individuals are deep clones, so the given population is not altered. Of a cluster of individuals, the given * ratio of individuals is kept, more precisely, the best one is kept while the remaining are selected randomly. All loners are kept. * * @param pop the population to work on * @param problem the target problem instance * @param sigmaCluster minimum clustering distance * @param funCalls number of function calls for the optimization step * @param keepClusterRatio of a cluster of individuals, this ratio of individuals is kept for optimization * @param mute the mutation operator to be used by the hill climber * @return a pair of the optimized population and the improvement in the mean fitness (not normed) achieved by the HC run */ public static Pair clusterHC(Population pop, AbstractOptimizationProblem problem, double sigmaCluster, int funCalls, double keepClusterRatio, InterfaceMutation mute) { Population clust = (Population)clusterBest(pop, new ClusteringDensityBased(sigmaCluster, 2), keepClusterRatio, KEEP_LONERS, BEST_RAND).clone(); // System.out.println("keeping " + clust.size() + " for hc...."); double[] meanFit = clust.getMeanFitness(); processWithHC(clust, problem, new EvaluationTerminator(pop.getFunctionCalls()+funCalls), mute); double improvement = PhenotypeMetric.euclidianDistance(meanFit, clust.getMeanFitness()); if (TRACE) System.out.println("improvement by " + improvement); return new Pair(clust, improvement); } // /** // * Do some post processing on a multimodal problem. If the real optima are known, only the number of // * found optima is printed. Otherwise, the population is clustered and for the population of cluster-representatives, // * some diversity measures and a fitness histogram are printed. // * // * @see Population.getPopulationMeasures() // * @see createFitNormHistogram // * @param mmProb the target problem // * @param pop the solution set // * @param sigmaCluster the min clustering distance // * @param out a PrintStream for data output // * @param minBnd lower fitness bound // * @param maxBnd upper fitness bound // * @param bins number of bins for the fitness histogram // * @return // */ // public static Population outputResult(AbstractOptimizationProblem mmProb, Population pop, double sigmaCluster, PrintStream out, double minBnd, double maxBnd, int bins, int hcSteps) { // ClusteringDensityBased clust = new ClusteringDensityBased(sigmaCluster); // clust.setMinimumGroupSize(2); // Population clusteredBest = clusterBest(pop, clust, 0, KEEP_LONERS, BEST_ONLY); // if (hcSteps > 0) { // HC post process // Population tmpPop = (Population)clusteredBest.clone(); // processWithHC(tmpPop, (AbstractOptimizationProblem)mmProb, hcSteps, 0.001); // clusteredBest = clusterBest(tmpPop, clust, 0, KEEP_LONERS, BEST_ONLY); // } // double[] meas = clusteredBest.getPopulationMeasures(); // int[] sols = createFitNormHistogram(clusteredBest, minBnd, maxBnd, bins); // out.println("measures: " + BeanInspector.toString(meas)); // out.println("solution hist.: " + BeanInspector.toString(sols)); // // Object[] bestArr = clusteredBest.toArray(); // for (Object locOpt : bestArr) { //// out.print((AbstractEAIndividual.getDefaultDataString((IndividualInterface)locOpt))); // out.println(AbstractEAIndividual.getDefaultStringRepresentation(((AbstractEAIndividual)locOpt))); // } // return clusteredBest; // } // public static Population outputResultKnown(InterfaceMultimodalProblemKnown mmProb, Population pop, double sigmaCluster, PrintStream out, double minBnd, double maxBnd, int bins) { // Population found = getFoundOptima(pop, ((InterfaceMultimodalProblemKnown)mmProb).getRealOptima(), ((InterfaceMultimodalProblemKnown)mmProb).getEpsilon(), true); // for (double epsilon=0.1; epsilon > 0.00000001; epsilon/=10.) { // //out.println("no optima found: " + ((InterfaceMultimodalProblemKnown)mmProb).getNumberOfFoundOptima(pop)); // out.println("found " + getFoundOptima(pop, ((InterfaceMultimodalProblemKnown)mmProb).getRealOptima(), epsilon, true).size() + " for epsilon = " + epsilon); // } // out.println("max peak ratio is " + mmProb.getMaximumPeakRatio(found)); // return found; // } /** * Do some data output for multimodal problems with known optima. The listener may be null, but then the method is * not really doing much at this state. */ public static void procMultiModalKnown(Population solutions, InterfaceMultimodalProblemKnown mmkProb, InterfaceTextListener listener) { // Population found = getFoundOptima(solutions, mmkProb.getRealOptima(), mmkProb.getEpsilon(), true); if (listener != null) { listener.println("default epsilon is " + mmkProb.getEpsilon()); listener.println("max peak ratio is " + mmkProb.getMaximumPeakRatio(getFoundOptima(solutions, mmkProb.getRealOptima(), mmkProb.getEpsilon(), true))); } for (double epsilon=0.1; epsilon > 0.00000001; epsilon/=10.) { // out.println("no optima found: " + ((InterfaceMultimodalProblemKnown)mmProb).getNumberOfFoundOptima(pop)); if (listener != null) listener.println("found " + getFoundOptima(solutions, mmkProb.getRealOptima(), epsilon, true).size() + " for epsilon = " + epsilon); } } /** * Universal post processing method, receiving parameter instance for specification. * Optional clustering and HC step, output contains population measures, fitness histogram and * a list of solutions after post processing. * * @param params * @param inputPop * @param problem * @param listener * @return the clustered, post-processed population */ public static Population postProcess(InterfacePostProcessParams params, Population inputPop, AbstractOptimizationProblem problem, InterfaceTextListener listener) { if (params.isDoPostProcessing()) { Population clusteredPop, outputPop; if (params.getPostProcessClusterSigma() > 0) { clusteredPop = (Population)PostProcess.clusterBest(inputPop, params.getPostProcessClusterSigma(), 0, PostProcess.KEEP_LONERS, PostProcess.BEST_ONLY).clone(); if (clusteredPop.size() < inputPop.size()) { if (listener != null) listener.println("Initial clustering reduced population size from " + inputPop.size() + " to " + clusteredPop.size()); } else if (listener != null) listener.println("Initial clustering yielded no size reduction."); } else clusteredPop = inputPop; if (params.getPostProcessSteps() > 0) { int stepsDone = processWithHC(clusteredPop, problem, params.getPostProcessSteps()); if (listener != null) listener.println("HC post processing: " + stepsDone + " steps done."); // some individuals may have now converged again if (params.getPostProcessClusterSigma() > 0) { // so if wished, cluster again. outputPop = (Population)PostProcess.clusterBest(clusteredPop, params.getPostProcessClusterSigma(), 0, PostProcess.KEEP_LONERS, PostProcess.BEST_ONLY).clone(); if (outputPop.size() < clusteredPop.size()) { if (listener != null) listener.println("Second clustering reduced population size from " + clusteredPop.size() + " to " + outputPop.size()); } else if (listener != null) listener.println("Second clustering yielded no size reduction."); } else outputPop = clusteredPop; } else outputPop = clusteredPop; double upBnd = PhenotypeMetric.norm(outputPop.getWorstEAIndividual().getFitness())*1.1; upBnd = Math.pow(10,Math.floor(Math.log10(upBnd)+1)); double lowBnd = 0; int[] sols = PostProcess.createFitNormHistogram(outputPop, lowBnd, upBnd, 20); // PostProcessInterim.outputResult((AbstractOptimizationProblem)goParams.getProblem(), outputPop, 0.01, System.out, 0, 2000, 20, goParams.getPostProcessSteps()); if (outputPop.size()>1) { if (listener != null) listener.println("measures: " + BeanInspector.toString(outputPop.getPopulationMeasures())); if (listener != null) listener.println("solution histogram in [" + lowBnd + "," + upBnd + "]: " + BeanInspector.toString(sols)); } //////////// multimodal data output? if (problem instanceof InterfaceMultimodalProblemKnown) procMultiModalKnown(outputPop, (InterfaceMultimodalProblemKnown)problem, listener); Population nBestPop = outputPop.getBestNIndividuals(params.getPrintNBest()); // n individuals are returned and sorted, all of them if n<=0 if (listener != null) listener.println("Best after post process:" + ((outputPop.size()>nBestPop.size()) ? ( "(first " + nBestPop.size() + " of " + outputPop.size() + ")") : "")); //////////// output some individual data if (listener != null) for (int i=0; i