package com.intellectualcrafters.plot.util.expiry; import com.google.common.base.Optional; import com.intellectualcrafters.plot.PS; import com.intellectualcrafters.plot.config.Settings; import com.intellectualcrafters.plot.flag.Flags; import com.intellectualcrafters.plot.generator.HybridUtils; import com.intellectualcrafters.plot.object.Plot; import com.intellectualcrafters.plot.object.RunnableVal; import com.intellectualcrafters.plot.util.MathMan; import com.intellectualcrafters.plot.util.TaskManager; import java.lang.reflect.Array; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class PlotAnalysis { public static boolean running = false; public int changes; public int faces; public int data; public int air; public int variety; public int changes_sd; public int faces_sd; public int data_sd; public int air_sd; public int variety_sd; private int complexity; public static PlotAnalysis getAnalysis(Plot plot, Settings.Auto_Clear settings) { Optional<List<Integer>> flag = plot.getFlag(Flags.ANALYSIS); if (flag.isPresent()) { PlotAnalysis analysis = new PlotAnalysis(); List<Integer> values = flag.get(); analysis.changes = values.get(0); // 2126 analysis.faces = values.get(1); // 90 analysis.data = values.get(2); // 0 analysis.air = values.get(3); // 19100 analysis.variety = values.get(4); // 266 analysis.changes_sd = values.get(5); // 2104 analysis.faces_sd = values.get(6); // 89 analysis.data_sd = values.get(7); // 0 analysis.air_sd = values.get(8); // 18909 analysis.variety_sd = values.get(9); // 263 analysis.complexity = settings != null ? analysis.getComplexity(settings) : 0; return analysis; } return null; } public static void analyzePlot(Plot plot, RunnableVal<PlotAnalysis> whenDone) { HybridUtils.manager.analyzePlot(plot, whenDone); } /** * This will set the optimal modifiers for the plot analysis based on the current plot ratings<br> * - Will be used to calibrate the threshold for plot clearing * @param whenDone * @param threshold */ public static void calcOptimalModifiers(final Runnable whenDone, final double threshold) { if (running) { PS.debug("Calibration task already in progress!"); return; } if (threshold <= 0 || threshold >= 1) { PS.debug("Invalid threshold provided! (Cannot be 0 or 100 as then there's no point calibrating)"); return; } running = true; PS.debug(" - Fetching all plots"); final ArrayList<Plot> plots = new ArrayList<>(PS.get().getPlots()); TaskManager.runTaskAsync(new Runnable() { @Override public void run() { Iterator<Plot> iterator = plots.iterator(); PS.debug(" - $1Reducing " + plots.size() + " plots to those with sufficient data"); while (iterator.hasNext()) { Plot plot = iterator.next(); if (plot.getSettings().ratings == null || plot.getSettings().getRatings().isEmpty()) { iterator.remove(); } else { plot.addRunning(); } } PS.debug(" - | Reduced to " + plots.size() + " plots"); if (plots.size() < 3) { PS.debug("Calibration cancelled due to insufficient comparison data, please try again later"); running = false; for (Plot plot : plots) { plot.removeRunning(); } return; } PS.debug(" - $1Analyzing plot contents (this may take a while)"); int[] changes = new int[plots.size()]; int[] faces = new int[plots.size()]; int[] data = new int[plots.size()]; int[] air = new int[plots.size()]; int[] variety = new int[plots.size()]; int[] changes_sd = new int[plots.size()]; int[] faces_sd = new int[plots.size()]; int[] data_sd = new int[plots.size()]; int[] air_sd = new int[plots.size()]; int[] variety_sd = new int[plots.size()]; final int[] ratings = new int[plots.size()]; final AtomicInteger mi = new AtomicInteger(0); Thread ratingAnalysis = new Thread(new Runnable() { @Override public void run() { for (; mi.intValue() < plots.size(); mi.incrementAndGet()) { int i = mi.intValue(); Plot plot = plots.get(i); ratings[i] = (int) ((plot.getAverageRating() + plot.getSettings().getRatings().size()) * 100); PS.debug(" | " + plot + " (rating) " + ratings[i]); } } }); ratingAnalysis.start(); ArrayDeque<Plot> plotsQueue = new ArrayDeque<>(plots); while (true) { final Plot queuePlot = plotsQueue.poll(); if (queuePlot == null) { break; } PS.debug(" | " + queuePlot); final Object lock = new Object(); TaskManager.runTask(new Runnable() { @Override public void run() { analyzePlot(queuePlot, new RunnableVal<PlotAnalysis>() { @Override public void run(PlotAnalysis value) { try { synchronized (this) { wait(10000); } } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock) { queuePlot.removeRunning(); lock.notify(); } } }); } }); try { synchronized (lock) { lock.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } PS.debug(" - $1Waiting on plot rating thread: " + mi.intValue() * 100 / plots.size() + "%"); try { ratingAnalysis.join(); } catch (InterruptedException e) { e.printStackTrace(); } PS.debug(" - $1Processing and grouping single plot analysis for bulk processing"); for (int i = 0; i < plots.size(); i++) { Plot plot = plots.get(i); PS.debug(" | " + plot); PlotAnalysis analysis = plot.getComplexity(null); changes[i] = analysis.changes; faces[i] = analysis.faces; data[i] = analysis.data; air[i] = analysis.air; variety[i] = analysis.variety; changes_sd[i] = analysis.changes_sd; faces_sd[i] = analysis.faces_sd; data_sd[i] = analysis.data_sd; air_sd[i] = analysis.air_sd; variety_sd[i] = analysis.variety_sd; } PS.debug(" - $1Calculating rankings"); int[] rankRatings = rank(ratings); int n = rankRatings.length; int optimalIndex = (int) Math.round((1 - threshold) * (n - 1)); PS.debug(" - $1Calculating rank correlation: "); PS.debug(" - The analyzed plots which were processed and put into bulk data will be compared and correlated to the plot ranking"); PS.debug(" - The calculated correlation constant will then be used to calibrate the threshold for auto plot clearing"); Settings.Auto_Clear settings = new Settings.Auto_Clear(); int[] rankChanges = rank(changes); int[] sdChanges = getSD(rankChanges, rankRatings); int[] varianceChanges = square(sdChanges); int sumChanges = sum(varianceChanges); double factorChanges = getCC(n, sumChanges); settings.CALIBRATION.CHANGES = factorChanges == 1 ? 0 : (int) (factorChanges * 1000 / MathMan.getMean(changes)); PS.debug(" - | changes " + factorChanges); int[] rankFaces = rank(faces); int[] sdFaces = getSD(rankFaces, rankRatings); int[] varianceFaces = square(sdFaces); int sumFaces = sum(varianceFaces); double factorFaces = getCC(n, sumFaces); settings.CALIBRATION.FACES = factorFaces == 1 ? 0 : (int) (factorFaces * 1000 / MathMan.getMean(faces)); PS.debug(" - | faces " + factorFaces); int[] rankData = rank(data); int[] sdData = getSD(rankData, rankRatings); int[] variance_data = square(sdData); int sum_data = sum(variance_data); double factor_data = getCC(n, sum_data); settings.CALIBRATION.DATA = factor_data == 1 ? 0 : (int) (factor_data * 1000 / MathMan.getMean(data)); PS.debug(" - | data " + factor_data); int[] rank_air = rank(air); int[] sd_air = getSD(rank_air, rankRatings); int[] variance_air = square(sd_air); int sum_air = sum(variance_air); double factor_air = getCC(n, sum_air); settings.CALIBRATION.AIR = factor_air == 1 ? 0 : (int) (factor_air * 1000 / MathMan.getMean(air)); PS.debug(" - | air " + factor_air); int[] rank_variety = rank(variety); int[] sd_variety = getSD(rank_variety, rankRatings); int[] variance_variety = square(sd_variety); int sum_variety = sum(variance_variety); double factor_variety = getCC(n, sum_variety); settings.CALIBRATION.VARIETY = factor_variety == 1 ? 0 : (int) (factor_variety * 1000 / MathMan.getMean(variety)); PS.debug(" - | variety " + factor_variety); int[] rank_changes_sd = rank(changes_sd); int[] sd_changes_sd = getSD(rank_changes_sd, rankRatings); int[] variance_changes_sd = square(sd_changes_sd); int sum_changes_sd = sum(variance_changes_sd); double factor_changes_sd = getCC(n, sum_changes_sd); settings.CALIBRATION.CHANGES_SD = factor_changes_sd == 1 ? 0 : (int) (factor_changes_sd * 1000 / MathMan.getMean(changes_sd)); PS.debug(" - | changes_sd " + factor_changes_sd); int[] rank_faces_sd = rank(faces_sd); int[] sd_faces_sd = getSD(rank_faces_sd, rankRatings); int[] variance_faces_sd = square(sd_faces_sd); int sum_faces_sd = sum(variance_faces_sd); double factor_faces_sd = getCC(n, sum_faces_sd); settings.CALIBRATION.FACES_SD = factor_faces_sd == 1 ? 0 : (int) (factor_faces_sd * 1000 / MathMan.getMean(faces_sd)); PS.debug(" - | faces_sd " + factor_faces_sd); int[] rank_data_sd = rank(data_sd); int[] sd_data_sd = getSD(rank_data_sd, rankRatings); int[] variance_data_sd = square(sd_data_sd); int sum_data_sd = sum(variance_data_sd); double factor_data_sd = getCC(n, sum_data_sd); settings.CALIBRATION.DATA_SD = factor_data_sd == 1 ? 0 : (int) (factor_data_sd * 1000 / MathMan.getMean(data_sd)); PS.debug(" - | data_sd " + factor_data_sd); int[] rank_air_sd = rank(air_sd); int[] sd_air_sd = getSD(rank_air_sd, rankRatings); int[] variance_air_sd = square(sd_air_sd); int sum_air_sd = sum(variance_air_sd); double factor_air_sd = getCC(n, sum_air_sd); settings.CALIBRATION.AIR_SD = factor_air_sd == 1 ? 0 : (int) (factor_air_sd * 1000 / MathMan.getMean(air_sd)); PS.debug(" - | air_sd " + factor_air_sd); int[] rank_variety_sd = rank(variety_sd); int[] sd_variety_sd = getSD(rank_variety_sd, rankRatings); int[] variance_variety_sd = square(sd_variety_sd); int sum_variety_sd = sum(variance_variety_sd); double factor_variety_sd = getCC(n, sum_variety_sd); settings.CALIBRATION.VARIETY_SD = factor_variety_sd == 1 ? 0 : (int) (factor_variety_sd * 1000 / MathMan.getMean(variety_sd)); PS.debug(" - | variety_sd " + factor_variety_sd); int[] complexity = new int[n]; PS.debug(" $1Calculating threshold"); int max = 0; int min = 0; for (int i = 0; i < n; i++) { Plot plot = plots.get(i); PlotAnalysis analysis = plot.getComplexity(settings); complexity[i] = analysis.complexity; if (analysis.complexity < min) { min = analysis.complexity; } else if (analysis.complexity > max) { max = analysis.complexity; } } int optimalComplexity = Integer.MAX_VALUE; if (min > 0 && max < 102400) { // If low size, use my fast ranking algorithm int[] rankComplexity = rank(complexity, max + 1); for (int i = 0; i < n; i++) { if (rankComplexity[i] == optimalIndex) { optimalComplexity = complexity[i]; break; } } logln("Complexity: "); logln(rankComplexity); logln("Ratings: "); logln(rankRatings); logln("Correlation: "); logln(getCC(n, sum(square(getSD(rankComplexity, rankRatings))))); if (optimalComplexity == Integer.MAX_VALUE) { PS.debug("Insufficient data to determine correlation! " + optimalIndex + " | " + n); running = false; for (Plot plot : plots) { plot.removeRunning(); } return; } } else { // Use the fast radix sort algorithm int[] sorted = complexity.clone(); sort(sorted); optimalComplexity = sorted[optimalIndex]; logln("Complexity: "); logln(complexity); logln("Ratings: "); logln(rankRatings); } // Save calibration PS.debug(" $1Saving calibration"); Settings.AUTO_CLEAR.put("auto-calibrated", settings); Settings.save(PS.get().worldsFile); PS.debug("$1Done!"); running = false; for (Plot plot : plots) { plot.removeRunning(); } whenDone.run(); } }); } public static void logln(Object obj) { PS.debug(log(obj)); } public static String log(Object obj) { String result = ""; if (obj.getClass().isArray()) { String prefix = ""; for (int i = 0; i < Array.getLength(obj); i++) { result += prefix + log(Array.get(obj, i)); prefix = ","; } return "( " + result + " )"; } else if (obj instanceof List<?>) { String prefix = ""; for (Object element : (List<?>) obj) { result += prefix + log(element); prefix = ","; } return "[ " + result + " ]"; } else { return obj.toString(); } } /** * Get correlation coefficient. * @return */ public static double getCC(int n, int sum) { return 1 - 6 * (double) sum / (n * (n * n - 1)); } /** * Sum of an array * @param array * @return */ public static int sum(int[] array) { int sum = 0; for (int value : array) { sum += value; } return sum; } /** * A simple array squaring algorithm. * - Used for calculating the variance * @param array * @return */ public static int[] square(int[] array) { array = array.clone(); for (int i = 0; i < array.length; i++) { array[i] *= array[i]; } return array; } /** * An optimized lossy standard deviation algorithm. * @param ranks * @return */ public static int[] getSD(int[]... ranks) { if (ranks.length == 0) { return null; } int[] result = new int[ranks[0].length]; for (int j = 0; j < ranks[0].length; j++) { int sum = 0; for (int[] rank : ranks) { sum += rank[j]; } int mean = sum / ranks.length; int sd = 0; for (int[] rank : ranks) { int value = rank[j]; sd += value < mean ? mean - value : value - mean; } result[j] = sd; } return result; } /** * An optimized algorithm for ranking a very specific set of inputs<br> * - Input is an array of int with a max size of 102400<br> * - A reduced sample space allows for sorting (and ranking in this case) in linear time * @param input * @return /** * An optimized algorithm for ranking a very specific set of inputs * @param input * @return */ public static int[] rank(int[] input) { return rank(input, 102400); } /** * An optimized algorithm for ranking a very specific set of inputs * @param input * @return */ public static int[] rank(int[] input, int size) { int[] cache = new int[size]; int max = 0; if (input.length < size) { for (int value : input) { if (value > max) { max = value; } cache[value]++; } } else { max = cache.length - 1; for (int value : input) { cache[value]++; } } int last = 0; for (int i = max; i >= 0; i--) { if (cache[i] != 0) { cache[i] += last; last = cache[i]; if (last == input.length) { break; } } } int[] ranks = new int[input.length]; for (int i = 0; i < input.length; i++) { int index = input[i]; ranks[i] = cache[index]; cache[index]--; } return ranks; } public static void sort(int[] input) { int SIZE = 10; List<Integer>[] bucket = new ArrayList[SIZE]; for (int i = 0; i < bucket.length; i++) { bucket[i] = new ArrayList<>(); } boolean maxLength = false; int placement = 1; while (!maxLength) { maxLength = true; for (Integer i : input) { int tmp = i / placement; bucket[tmp % SIZE].add(i); if (maxLength && tmp > 0) { maxLength = false; } } int a = 0; for (int b = 0; b < SIZE; b++) { for (Integer i : bucket[b]) { input[a++] = i; } bucket[b].clear(); } placement *= SIZE; } } public List<Integer> asList() { return Arrays.asList(this.changes, this.faces, this.data, this.air, this.variety, this.changes_sd, this.faces_sd, this.data_sd, this.air_sd, this.variety_sd); } public int getComplexity(Settings.Auto_Clear settings) { Settings.Auto_Clear.CALIBRATION modifiers = settings.CALIBRATION; if (this.complexity != 0) { return this.complexity; } this.complexity = this.changes * modifiers.CHANGES + this.faces * modifiers.FACES + this.data * modifiers.DATA + this.air * modifiers.AIR + this.variety * modifiers.VARIETY + this.changes_sd * modifiers.CHANGES_SD + this.faces_sd * modifiers.FACES_SD + this.data_sd * modifiers.DATA_SD + this.air_sd * modifiers.AIR_SD + this.variety_sd * modifiers.VARIETY_SD; return this.complexity; } }